Merge branch 'feature_bugfix/251104_event' of https://git.valuefrontier.cn/vf/vf_react into feature_bugfix/251104_event
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 (
|
||||
<Box
|
||||
pl={2}
|
||||
@@ -47,7 +55,7 @@ const EventDetailScrollPanel = ({
|
||||
}}
|
||||
>
|
||||
{selectedEvent ? (
|
||||
<DynamicNewsDetailPanel event={selectedEvent} />
|
||||
<DynamicNewsDetailPanel event={selectedEvent} showHeader={shouldShowHeader} />
|
||||
) : (
|
||||
<Center h="100%" minH="400px">
|
||||
<VStack spacing={4}>
|
||||
|
||||
@@ -112,6 +112,7 @@ const VerticalModeLayout = ({
|
||||
timelineStyle={getTimelineBoxStyle()}
|
||||
borderColor={borderColor}
|
||||
indicatorSize={layoutMode === 'detail' ? 'default' : 'comfortable'}
|
||||
layout="vertical"
|
||||
/>
|
||||
))}
|
||||
</VStack>
|
||||
@@ -161,6 +162,7 @@ const VerticalModeLayout = ({
|
||||
{/* 详情面板 */}
|
||||
<EventDetailScrollPanel
|
||||
key={detailPanelKey}
|
||||
detailMode="no-header"
|
||||
selectedEvent={selectedEvent}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -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 ? <ChevronUpIcon /> : <ChevronDownIcon />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
justify="space-between"
|
||||
align="center"
|
||||
cursor="pointer"
|
||||
onClick={onToggle}
|
||||
cursor={showModeToggle ? 'default' : 'pointer'}
|
||||
onClick={showModeToggle ? undefined : onToggle}
|
||||
p={3}
|
||||
bg={sectionBg}
|
||||
borderRadius="md"
|
||||
_hover={{ bg: hoverBg }}
|
||||
_hover={showModeToggle ? {} : { bg: hoverBg }}
|
||||
transition="background 0.2s"
|
||||
>
|
||||
<HStack spacing={2}>
|
||||
@@ -54,12 +86,32 @@ const CollapsibleHeader = ({ title, isOpen, onToggle, count = null, subscription
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
<IconButton
|
||||
icon={isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label={isOpen ? '收起' : '展开'}
|
||||
/>
|
||||
|
||||
{/* 只有 showModeToggle=true 时才显示模式切换按钮 */}
|
||||
{showModeToggle && onModeToggle && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
colorScheme="blue"
|
||||
rightIcon={getButtonIcon()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onModeToggle(e);
|
||||
}}
|
||||
>
|
||||
{getButtonText()}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* showModeToggle=false 时显示原有的 IconButton */}
|
||||
{!showModeToggle && (
|
||||
<IconButton
|
||||
icon={isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label={isOpen ? '收起' : '展开'}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<Box>
|
||||
<CollapsibleHeader
|
||||
title={title}
|
||||
isOpen={isOpen}
|
||||
onToggle={handleToggle}
|
||||
count={count}
|
||||
subscriptionBadge={subscriptionBadge}
|
||||
/>
|
||||
// 处理模式切换
|
||||
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 (
|
||||
<Box mt={2} bg={sectionBg} p={3} borderRadius="md">
|
||||
{simpleContent}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染详细模式
|
||||
const renderDetailedMode = () => {
|
||||
return (
|
||||
<Collapse
|
||||
in={isOpen && !isLocked}
|
||||
animateOpacity
|
||||
@@ -61,6 +98,25 @@ const CollapsibleSection = ({
|
||||
{children}
|
||||
</Box>
|
||||
</Collapse>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<CollapsibleHeader
|
||||
title={title}
|
||||
isOpen={isOpen}
|
||||
onToggle={handleToggle}
|
||||
count={count}
|
||||
subscriptionBadge={subscriptionBadge}
|
||||
showModeToggle={showModeToggle}
|
||||
currentMode={displayMode}
|
||||
onModeToggle={handleModeToggle}
|
||||
isLocked={isLocked}
|
||||
/>
|
||||
|
||||
{/* 根据当前模式渲染对应内容 */}
|
||||
{displayMode === 'simple' ? renderSimpleMode() : renderDetailedMode()}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<HStack
|
||||
position="absolute"
|
||||
top={3}
|
||||
right={3}
|
||||
spacing={3}
|
||||
zIndex={1}
|
||||
>
|
||||
{/* 重要性徽章 - 与 EventHeaderInfo 样式一致,尺寸略小 */}
|
||||
<Badge
|
||||
px={3}
|
||||
py={1.5}
|
||||
borderRadius="full"
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
bgGradient={
|
||||
importance.level === 'S' ? 'linear(to-r, red.500, red.700)' :
|
||||
importance.level === 'A' ? 'linear(to-r, orange.500, orange.700)' :
|
||||
importance.level === 'B' ? 'linear(to-r, blue.500, blue.700)' :
|
||||
'linear(to-r, gray.500, gray.700)'
|
||||
}
|
||||
color="white"
|
||||
boxShadow="lg"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
>
|
||||
<Icon as={importance.icon} boxSize={4} />
|
||||
<Text>重要性:{getImportanceText()}</Text>
|
||||
</Badge>
|
||||
|
||||
{/* 浏览次数 - 添加容器背景以提高可读性 */}
|
||||
<HStack
|
||||
spacing={1}
|
||||
bg={viewCountBg}
|
||||
px={2}
|
||||
py={1}
|
||||
borderRadius="md"
|
||||
boxShadow="sm"
|
||||
>
|
||||
<ViewIcon color="gray.400" boxSize={4} />
|
||||
<Text fontSize="sm" color={viewCountTextColor} whiteSpace="nowrap">
|
||||
{(event.view_count || 0).toLocaleString()}
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
{/* 关注按钮 */}
|
||||
<EventFollowButton
|
||||
isFollowing={isFollowing}
|
||||
followerCount={followerCount}
|
||||
onToggle={onToggleFollow}
|
||||
size="sm"
|
||||
showCount={true}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompactMetaBar;
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
Text,
|
||||
Spinner,
|
||||
Center,
|
||||
Wrap,
|
||||
WrapItem,
|
||||
useColorModeValue,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
@@ -19,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';
|
||||
@@ -32,8 +36,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');
|
||||
@@ -111,8 +116,8 @@ const DynamicNewsDetailPanel = ({ event }) => {
|
||||
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);
|
||||
@@ -199,14 +204,18 @@ const DynamicNewsDetailPanel = ({ event }) => {
|
||||
// 🎯 加载事件详情(增加浏览量)
|
||||
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);
|
||||
}
|
||||
@@ -278,21 +287,34 @@ const DynamicNewsDetailPanel = ({ event }) => {
|
||||
|
||||
return (
|
||||
<Card bg={cardBg} borderColor={borderColor} borderWidth="1px">
|
||||
<CardBody>
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{/* 头部信息区 - 优先使用完整详情数据(包含最新浏览量) */}
|
||||
<EventHeaderInfo
|
||||
<CardBody position="relative">
|
||||
{/* 无头部模式:显示右上角精简信息栏 */}
|
||||
{!showHeader && (
|
||||
<CompactMetaBar
|
||||
event={fullEventDetail || event}
|
||||
importance={importance}
|
||||
isFollowing={isFollowing}
|
||||
followerCount={followerCount}
|
||||
onToggleFollow={handleToggleFollow}
|
||||
/>
|
||||
)}
|
||||
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{/* 头部信息区 - 优先使用完整详情数据(包含最新浏览量) - 可配置显示/隐藏 */}
|
||||
{showHeader && (
|
||||
<EventHeaderInfo
|
||||
event={fullEventDetail || event}
|
||||
importance={importance}
|
||||
isFollowing={isFollowing}
|
||||
followerCount={followerCount}
|
||||
onToggleFollow={handleToggleFollow}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 事件描述 */}
|
||||
<EventDescriptionSection description={event.description} />
|
||||
|
||||
{/* 相关股票(可折叠) - 懒加载 - 需要 PRO 权限 */}
|
||||
{/* 相关股票(可折叠) - 懒加载 - 需要 PRO 权限 - 支持精简/详细模式 */}
|
||||
<CollapsibleSection
|
||||
title="相关股票"
|
||||
isOpen={isStocksOpen}
|
||||
@@ -306,6 +328,27 @@ const DynamicNewsDetailPanel = ({ event }) => {
|
||||
})()}
|
||||
isLocked={!canAccessStocks}
|
||||
onLockedClick={() => handleLockedClick('相关股票', 'pro')}
|
||||
showModeToggle={canAccessStocks}
|
||||
defaultMode="detailed"
|
||||
simpleContent={
|
||||
loading.stocks || loading.quotes ? (
|
||||
<Center py={2}>
|
||||
<Spinner size="sm" color="blue.500" />
|
||||
<Text ml={2} color={textColor} fontSize="sm">加载股票数据中...</Text>
|
||||
</Center>
|
||||
) : (
|
||||
<Wrap spacing={4}>
|
||||
{stocks?.map((stock, index) => (
|
||||
<WrapItem key={index}>
|
||||
<CompactStockItem
|
||||
stock={stock}
|
||||
quote={quotes[stock.stock_code]}
|
||||
/>
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
)
|
||||
}
|
||||
>
|
||||
{loading.stocks || loading.quotes ? (
|
||||
<Center py={4}>
|
||||
|
||||
@@ -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 = ({
|
||||
</Box>
|
||||
|
||||
{/* 简单模式:横向卡片列表(总是显示) */}
|
||||
<Flex gap={2} flexWrap="wrap" mb={isExpanded ? 3 : 0}>
|
||||
{concepts.map((concept, index) => (
|
||||
<SimpleConceptCard
|
||||
key={index}
|
||||
concept={concept}
|
||||
onClick={handleConceptClick}
|
||||
getRelevanceColor={getRelevanceColor}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
|
||||
{/* 详细模式:卡片网格(可折叠) */}
|
||||
<Collapse in={isExpanded} animateOpacity>
|
||||
{/* 详细概念卡片网格 */}
|
||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
||||
{hasNoConcepts ? (
|
||||
<Box mb={isExpanded ? 3 : 0}>
|
||||
{error ? (
|
||||
<Text color="red.500" fontSize="sm">{error}</Text>
|
||||
) : (
|
||||
<Text color={textColor} fontSize="sm">暂无相关概念数据</Text>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex gap={2} flexWrap="wrap" mb={isExpanded ? 3 : 0}>
|
||||
{concepts.map((concept, index) => (
|
||||
<DetailedConceptCard
|
||||
<SimpleConceptCard
|
||||
key={index}
|
||||
concept={concept}
|
||||
onClick={handleConceptClick}
|
||||
getRelevanceColor={getRelevanceColor}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{/* 详细模式:卡片网格(可折叠) */}
|
||||
<Collapse in={isExpanded} animateOpacity>
|
||||
{hasNoConcepts ? (
|
||||
<Box py={4}>
|
||||
{error ? (
|
||||
<Text color="red.500" fontSize="sm" textAlign="center">{error}</Text>
|
||||
) : (
|
||||
<Text color={textColor} fontSize="sm" textAlign="center">暂无详细数据</Text>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
/* 详细概念卡片网格 */
|
||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
||||
{concepts.map((concept, index) => (
|
||||
<DetailedConceptCard
|
||||
key={index}
|
||||
concept={concept}
|
||||
onClick={handleConceptClick}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -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<Object>} 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 (
|
||||
<VStack align="stretch" spacing={4}>
|
||||
{/* 模式切换按钮 */}
|
||||
<Flex justify="flex-end">
|
||||
<ButtonGroup size="sm" isAttached variant="outline">
|
||||
<Button
|
||||
leftIcon={<ViewIcon />}
|
||||
colorScheme={viewMode === 'detail' ? 'blue' : 'gray'}
|
||||
variant={viewMode === 'detail' ? 'solid' : 'outline'}
|
||||
onClick={() => setViewMode('detail')}
|
||||
>
|
||||
详情模式
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<ViewOffIcon />}
|
||||
colorScheme={viewMode === 'compact' ? 'blue' : 'gray'}
|
||||
variant={viewMode === 'compact' ? 'solid' : 'outline'}
|
||||
onClick={() => setViewMode('compact')}
|
||||
>
|
||||
精简模式
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
|
||||
{/* 详情模式 */}
|
||||
{viewMode === 'detail' && (
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{stocks.map((stock, index) => (
|
||||
<StockListItem
|
||||
key={index}
|
||||
stock={stock}
|
||||
quote={quotes[stock.stock_code]}
|
||||
eventTime={eventTime}
|
||||
isInWatchlist={watchlistSet.has(stock.stock_code)}
|
||||
onWatchlistToggle={onWatchlistToggle}
|
||||
/>
|
||||
))}
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
{/* 精简模式 */}
|
||||
{viewMode === 'compact' && (
|
||||
<Wrap spacing={4}>
|
||||
{stocks.map((stock, index) => (
|
||||
<WrapItem key={index}>
|
||||
<CompactStockItem
|
||||
stock={stock}
|
||||
quote={quotes[stock.stock_code]}
|
||||
/>
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
)}
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{stocks.map((stock, index) => (
|
||||
<StockListItem
|
||||
key={index}
|
||||
stock={stock}
|
||||
quote={quotes[stock.stock_code]}
|
||||
eventTime={eventTime}
|
||||
isInWatchlist={watchlistSet.has(stock.stock_code)}
|
||||
onWatchlistToggle={onWatchlistToggle}
|
||||
/>
|
||||
))}
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 = ({
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* 关联描述(单行显示,点击展开)- 占据更多空间 */}
|
||||
{relationText && relationText !== '--' && (
|
||||
<Tooltip
|
||||
label={isDescExpanded ? "点击收起" : "点击展开完整描述"}
|
||||
placement="top"
|
||||
hasArrow
|
||||
bg="gray.600"
|
||||
color="white"
|
||||
fontSize="xs"
|
||||
>
|
||||
<Box
|
||||
flex={1}
|
||||
minW={0}
|
||||
onClick={(e) => {
|
||||
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"
|
||||
>
|
||||
{/* 去掉"关联描述"标题 */}
|
||||
<Collapse in={isDescExpanded} startingHeight={40}>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color={nameColor}
|
||||
lineHeight="1.6"
|
||||
{/* 关联描述 - 升级和降级处理 */}
|
||||
{stock.relation_desc && (
|
||||
<Box flex={1} minW={0}>
|
||||
{stock.relation_desc?.data ? (
|
||||
// 升级:带引用来源的版本
|
||||
<CitedContent
|
||||
data={stock.relation_desc}
|
||||
title=""
|
||||
showAIBadge={true}
|
||||
containerStyle={{
|
||||
backgroundColor: useColorModeValue('#f7fafc', 'rgba(45, 55, 72, 0.6)'),
|
||||
borderRadius: '8px',
|
||||
padding: '0',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
// 降级:纯文本版本(保留展开/收起功能)
|
||||
<Tooltip
|
||||
label={isDescExpanded ? "点击收起" : "点击展开完整描述"}
|
||||
placement="top"
|
||||
hasArrow
|
||||
bg="gray.600"
|
||||
color="white"
|
||||
fontSize="xs"
|
||||
>
|
||||
<Box
|
||||
onClick={(e) => {
|
||||
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}
|
||||
</Text>
|
||||
</Collapse>
|
||||
{isDescExpanded && (
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color="gray.500"
|
||||
mt={2}
|
||||
fontStyle="italic"
|
||||
>
|
||||
⚠️ AI生成,仅供参考
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
{/* 去掉"关联描述"标题 */}
|
||||
<Collapse in={isDescExpanded} startingHeight={40}>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color={nameColor}
|
||||
lineHeight="1.6"
|
||||
>
|
||||
{relationText}
|
||||
</Text>
|
||||
</Collapse>
|
||||
|
||||
{/* 提示信息 */}
|
||||
{isDescExpanded && (
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color="gray.500"
|
||||
mt={2}
|
||||
fontStyle="italic"
|
||||
>
|
||||
⚠️ AI生成,仅供参考
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
{/* 右侧事件卡片容器(带印章) */}
|
||||
|
||||
@@ -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 = ({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* 历史事件卡片网格 */}
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={4}>
|
||||
{/* 历史事件卡片列表 - 混合布局 */}
|
||||
<VStack spacing={3} align="stretch">
|
||||
{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"
|
||||
>
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{/* 事件名称 */}
|
||||
<Text
|
||||
fontSize="md"
|
||||
fontWeight="bold"
|
||||
color={useColorModeValue('blue.600', 'blue.400')}
|
||||
noOfLines={2}
|
||||
lineHeight="1.4"
|
||||
cursor="pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCardClick(event);
|
||||
}}
|
||||
_hover={{ textDecoration: 'underline' }}
|
||||
>
|
||||
{event.title || '未命名事件'}
|
||||
</Text>
|
||||
|
||||
{/* 日期 + Badges */}
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
<Text fontSize="sm" color={textSecondary}>
|
||||
{formatDate(getEventDate(event))}
|
||||
</Text>
|
||||
<Text fontSize="sm" color={textSecondary}>
|
||||
({getRelativeTime(getEventDate(event))})
|
||||
</Text>
|
||||
{event.relevance && (
|
||||
<Badge colorScheme="blue" size="sm">
|
||||
相关度: {event.relevance}
|
||||
</Badge>
|
||||
)}
|
||||
{event.importance && (
|
||||
<Badge colorScheme={importanceColor} size="sm">
|
||||
重要性: {event.importance}
|
||||
</Badge>
|
||||
)}
|
||||
{event.avg_change_pct !== undefined && event.avg_change_pct !== null && (
|
||||
<Badge
|
||||
colorScheme={event.avg_change_pct > 0 ? 'red' : event.avg_change_pct < 0 ? 'green' : 'gray'}
|
||||
size="sm"
|
||||
<VStack align="stretch" spacing={2} p={3}>
|
||||
{/* 顶部区域:左侧(标题+时间) + 右侧(按钮) */}
|
||||
<HStack align="flex-start" spacing={3}>
|
||||
{/* 左侧:标题 + 时间信息(允许折行) */}
|
||||
<VStack flex="1" align="flex-start" spacing={1}>
|
||||
{/* 标题 */}
|
||||
<Text
|
||||
fontSize="md"
|
||||
fontWeight="bold"
|
||||
color={useColorModeValue('blue.600', 'blue.400')}
|
||||
lineHeight="1.4"
|
||||
cursor="pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCardClick(event);
|
||||
}}
|
||||
_hover={{ textDecoration: 'underline' }}
|
||||
>
|
||||
涨幅: {event.avg_change_pct > 0 ? '+' : ''}{event.avg_change_pct.toFixed(2)}%
|
||||
</Badge>
|
||||
)}
|
||||
{event.title || '未命名事件'}
|
||||
</Text>
|
||||
|
||||
{/* 时间 + Badges(允许折行) */}
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
<Text fontSize="sm" color={textSecondary}>
|
||||
{formatDate(getEventDate(event))}
|
||||
</Text>
|
||||
<Text fontSize="sm" color={textSecondary}>
|
||||
({getRelativeTime(getEventDate(event))})
|
||||
</Text>
|
||||
{event.importance && (
|
||||
<Badge colorScheme={importanceColor} size="sm">
|
||||
重要性: {event.importance}
|
||||
</Badge>
|
||||
)}
|
||||
{event.avg_change_pct !== undefined && event.avg_change_pct !== null && (
|
||||
<Badge
|
||||
colorScheme={event.avg_change_pct > 0 ? 'red' : event.avg_change_pct < 0 ? 'green' : 'gray'}
|
||||
size="sm"
|
||||
>
|
||||
涨幅: {event.avg_change_pct > 0 ? '+' : ''}{event.avg_change_pct.toFixed(2)}%
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
|
||||
{/* 右侧:相关股票按钮 */}
|
||||
<Button
|
||||
size="sm"
|
||||
leftIcon={<Icon as={FaChartLine} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleViewStocks(event);
|
||||
}}
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
flexShrink={0}
|
||||
>
|
||||
相关股票
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
{/* 事件描述 */}
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color={nameColor}
|
||||
lineHeight="1.6"
|
||||
noOfLines={4}
|
||||
>
|
||||
{getEventContent(event) ? `${getEventContent(event)}(AI合成)` : '暂无内容'}
|
||||
</Text>
|
||||
|
||||
{/* 相关股票按钮 */}
|
||||
<Button
|
||||
size="sm"
|
||||
leftIcon={<Icon as={FaChartLine} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleViewStocks(event);
|
||||
}}
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
width="full"
|
||||
>
|
||||
相关股票
|
||||
</Button>
|
||||
{/* 底部:描述(独占整行)- 升级和降级处理 */}
|
||||
<Box>
|
||||
{(() => {
|
||||
const content = getEventContent(event);
|
||||
// 检查是否有 data 结构(升级版本)
|
||||
if (content && typeof content === 'object' && content.data) {
|
||||
return (
|
||||
<CitedContent
|
||||
data={content}
|
||||
title=""
|
||||
showAIBadge={true}
|
||||
containerStyle={{
|
||||
backgroundColor: useColorModeValue('#f7fafc', 'rgba(45, 55, 72, 0.6)'),
|
||||
borderRadius: '8px',
|
||||
padding: '0',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// 降级版本:纯文本
|
||||
return (
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color={nameColor}
|
||||
lineHeight="1.6"
|
||||
noOfLines={2}
|
||||
>
|
||||
{content ? `${content}(AI合成)` : '暂无内容'}
|
||||
</Text>
|
||||
);
|
||||
})()}
|
||||
</Box>
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</SimpleGrid>
|
||||
</VStack>
|
||||
|
||||
{/* 相关股票 Modal - 条件渲染 */}
|
||||
{stocksModalOpen && (
|
||||
|
||||
Reference in New Issue
Block a user