Merge branch 'feature_2025/251121_h5UI' into feature_2025/251117_pref

* feature_2025/251121_h5UI:
  feat: 传导练UI调整
  fix: UI调试
  fix: 调整相关概念卡片UI
  fix: 文案调整
  fix: AI合成h5换行,pc一行,评论标题上方margin去掉
  fix: 调整AI合成UI
  fix: 分时图UI调整
  fix:事件详情弹窗UI
  fix:调整客服UI
  fix: 事件详情弹窗UI调整
  fix: 事件详情弹窗UI调整 重要性h5不展示 事件列表卡片间距调整
  fix: h5 去掉通知弹窗引导
  fix: 关注按钮UI调整
This commit is contained in:
zdl
2025-11-28 07:15:11 +08:00
18 changed files with 497 additions and 284 deletions

View File

@@ -63,6 +63,59 @@ const BytedeskWidget = ({
bytedesk.init(); bytedesk.init();
widgetRef.current = bytedesk; widgetRef.current = bytedesk;
// ⚡ H5 端样式适配:使用 MutationObserver 立即应用样式(避免闪烁)
const isMobile = window.innerWidth <= 768;
const applyBytedeskStyles = () => {
const allElements = document.querySelectorAll('body > div');
allElements.forEach(el => {
const style = window.getComputedStyle(el);
// 检查是否是右下角固定定位的元素Bytedesk 按钮)
if (style.position === 'fixed' && style.right && style.bottom) {
const rightVal = parseInt(style.right);
const bottomVal = parseInt(style.bottom);
if (rightVal >= 0 && rightVal < 100 && bottomVal >= 0 && bottomVal < 100) {
// H5 端设置按钮尺寸为 48x48只执行一次
if (isMobile && !el.dataset.bytedeskStyled) {
el.dataset.bytedeskStyled = 'true';
const button = el.querySelector('button');
if (button) {
button.style.width = '48px';
button.style.height = '48px';
button.style.minWidth = '48px';
button.style.minHeight = '48px';
}
}
// 提示框 3 秒后隐藏(查找白色气泡框)
const children = el.querySelectorAll('div');
children.forEach(child => {
if (child.dataset.bytedeskTooltip) return; // 已处理过
const childStyle = window.getComputedStyle(child);
// 白色背景的提示框
if (childStyle.backgroundColor === 'rgb(255, 255, 255)') {
child.dataset.bytedeskTooltip = 'true';
setTimeout(() => {
child.style.transition = 'opacity 0.3s';
child.style.opacity = '0';
setTimeout(() => child.remove(), 300);
}, 3000);
}
});
}
}
});
};
// 立即执行一次
applyBytedeskStyles();
// 监听 DOM 变化,新元素出现时立即应用样式
const observer = new MutationObserver(applyBytedeskStyles);
observer.observe(document.body, { childList: true, subtree: true });
// 5 秒后停止监听(避免性能问题)
setTimeout(() => observer.disconnect(), 5000);
// ⚡ 屏蔽 STOMP WebSocket 错误日志(不影响功能) // ⚡ 屏蔽 STOMP WebSocket 错误日志(不影响功能)
const originalConsoleError = console.error; const originalConsoleError = console.error;
console.error = function(...args) { console.error = function(...args) {

View File

@@ -82,29 +82,9 @@ const CitedContent = ({
...containerStyle ...containerStyle
}} }}
> >
{/* AI 标识 - 固定在右上角 */}
{showAIBadge && (
<Tag
icon={<RobotOutlined />}
color="purple"
style={{
position: 'absolute',
top: 12,
right: 12,
margin: 0,
zIndex: 10,
fontSize: 12,
padding: '2px 8px'
}}
className="ai-badge-responsive"
>
AI合成
</Tag>
)}
{/* 标题栏 */} {/* 标题栏 */}
{title && ( {title && (
<div style={{ marginBottom: 12, paddingRight: 80 }}> <div style={{ marginBottom: 12 }}>
<Text strong style={{ fontSize: 14, color: finalTitleColor }}> <Text strong style={{ fontSize: 14, color: finalTitleColor }}>
{title} {title}
</Text> </Text>
@@ -112,10 +92,24 @@ const CitedContent = ({
)} )}
{/* 带引用的文本内容 */} {/* 带引用的文本内容 */}
<div style={{ <div style={{ lineHeight: 1.8 }}>
lineHeight: 1.8, {/* AI 标识 - 行内显示在文字前面 */}
paddingRight: title ? 0 : (showAIBadge ? 80 : 0) {showAIBadge && (
}}> <Tag
icon={<RobotOutlined />}
color="purple"
style={{
fontSize: 12,
padding: '2px 8px',
marginRight: 8,
verticalAlign: 'middle',
display: 'inline-flex',
}}
className="ai-badge-responsive"
>
AI合成
</Tag>
)}
{/* 前缀标签(如果有) */} {/* 前缀标签(如果有) */}
{prefix && ( {prefix && (
<Text style={{ <Text style={{

View File

@@ -571,8 +571,8 @@ export const NotificationProvider = ({ children }) => {
logger.info('NotificationContext', 'Auto-requesting browser permission on notification'); logger.info('NotificationContext', 'Auto-requesting browser permission on notification');
await requestBrowserPermission(); await requestBrowserPermission();
} }
// 如果权限是denied已拒绝提供设置指引 // 如果权限是denied已拒绝提供设置指引(仅 PC 端显示)
else if (browserPermission === 'denied') { else if (browserPermission === 'denied' && !isMobile) {
const toastId = 'browser-permission-denied-guide'; const toastId = 'browser-permission-denied-guide';
if (!toast.isActive(toastId)) { if (!toast.isActive(toastId)) {
toast({ toast({

View File

@@ -36,3 +36,37 @@ iframe[src*="/visitor/"] {
[class*="bytedesk-badge"] { [class*="bytedesk-badge"] {
z-index: 1000000 !important; z-index: 1000000 !important;
} }
/* ========== H5 端客服组件整体缩小 ========== */
@media (max-width: 768px) {
/* 整个客服容器缩小(包括按钮和提示框) */
[class*="bytedesk"],
[id*="bytedesk"],
[class*="BytedeskWeb"] {
transform: scale(0.7) !important;
transform-origin: bottom right !important;
}
}
/* ========== 提示框 3 秒后自动消失 ========== */
/* 提示框("在线客服 点击咨询"气泡)- 扩展选择器 */
[class*="bytedesk-bubble"],
[class*="bytedesk-tooltip"],
[class*="BytedeskWeb"] [class*="bubble"],
[class*="BytedeskWeb"] [class*="tooltip"],
[class*="bytedesk"] > div:not(button):not(iframe),
[class*="BytedeskWeb"] > div:not(button):not(iframe),
[id*="bytedesk"] > div:not(button):not(iframe) {
animation: bytedeskFadeOut 0.3s ease-out 3s forwards !important;
}
@keyframes bytedeskFadeOut {
from {
opacity: 1;
visibility: visible;
}
to {
opacity: 0;
visibility: hidden;
}
}

View File

@@ -543,7 +543,7 @@ const [currentMode, setCurrentMode] = useState('vertical');
<Flex justify="space-between" align="center"> <Flex justify="space-between" align="center">
{/* 左侧:标题 + 模式切换按钮 */} {/* 左侧:标题 + 模式切换按钮 */}
<HStack spacing={4}> <HStack spacing={4}>
<Heading size="md" color={PROFESSIONAL_COLORS.text.primary}> <Heading size={isMobile ? "sm" : "md"} color={PROFESSIONAL_COLORS.text.primary}>
<HStack spacing={2}> <HStack spacing={2}>
<TimeIcon color={PROFESSIONAL_COLORS.gold[500]} /> <TimeIcon color={PROFESSIONAL_COLORS.gold[500]} />
<Text bgGradient={PROFESSIONAL_COLORS.gradients.gold} bgClip="text">实时要闻·动态追踪</Text> <Text bgGradient={PROFESSIONAL_COLORS.gradients.gold} bgClip="text">实时要闻·动态追踪</Text>
@@ -610,7 +610,9 @@ const [currentMode, setCurrentMode] = useState('vertical');
<CardBody <CardBody
ref={cardBodyRef} ref={cardBodyRef}
position="relative" position="relative"
pt={4} pt={0}
px={0}
mx={0}
display="flex" display="flex"
flexDirection="column" flexDirection="column"
overflow="visible" overflow="visible"

View File

@@ -9,18 +9,12 @@ import {
Center, Center,
Text, Text,
useBreakpointValue, useBreakpointValue,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalCloseButton,
useDisclosure useDisclosure
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { InfoIcon } from '@chakra-ui/icons'; import { InfoIcon } from '@chakra-ui/icons';
import HorizontalDynamicNewsEventCard from '../EventCard/HorizontalDynamicNewsEventCard'; import HorizontalDynamicNewsEventCard from '../EventCard/HorizontalDynamicNewsEventCard';
import EventDetailScrollPanel from './EventDetailScrollPanel'; import EventDetailScrollPanel from './EventDetailScrollPanel';
import DynamicNewsDetailPanel from '../DynamicNewsDetail/DynamicNewsDetailPanel'; import EventDetailModal from '../EventDetailModal';
/** /**
* 纵向分栏模式布局 * 纵向分栏模式布局
@@ -165,20 +159,11 @@ const VerticalModeLayout = React.memo(({
{/* 移动端详情弹窗 */} {/* 移动端详情弹窗 */}
{isMobile && ( {isMobile && (
<Modal isOpen={isMobileModalOpen} onClose={onMobileModalClose} size="full" scrollBehavior="inside"> <EventDetailModal
<ModalOverlay bg="blackAlpha.800" backdropFilter="blur(10px)" /> open={isMobileModalOpen}
<ModalContent maxW="100vw" m={0} borderRadius={0}> onClose={onMobileModalClose}
<ModalHeader bg="gray.900" color="white" borderBottom="1px solid" borderColor="gray.700"> event={mobileSelectedEvent}
{mobileSelectedEvent?.title || '事件详情'} />
</ModalHeader>
<ModalCloseButton color="white" />
<ModalBody p={0} bg="gray.900">
{mobileSelectedEvent && (
<DynamicNewsDetailPanel event={mobileSelectedEvent} showHeader={false} />
)}
</ModalBody>
</ModalContent>
</Modal>
)} )}
</Flex> </Flex>
); );

View File

@@ -47,7 +47,7 @@ const CompactMetaBar = ({ event, importance, isFollowing, followerCount, onToggl
spacing={3} spacing={3}
zIndex={1} zIndex={1}
> >
{/* 重要性徽章 - 与 EventHeaderInfo 样式一致,尺寸略小 */} {/* 重要性徽章 - 与 EventHeaderInfo 样式一致,尺寸略小 - H5 隐藏 */}
<Badge <Badge
px={3} px={3}
py={1.5} py={1.5}
@@ -62,7 +62,7 @@ const CompactMetaBar = ({ event, importance, isFollowing, followerCount, onToggl
} }
color="white" color="white"
boxShadow="lg" boxShadow="lg"
display="flex" display={{ base: 'none', lg: 'flex' }}
alignItems="center" alignItems="center"
gap={1} gap={1}
> >

View File

@@ -2,13 +2,15 @@
// 精简模式股票卡片组件(浮动卡片样式) // 精简模式股票卡片组件(浮动卡片样式)
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux';
import { import {
Box, Box,
Text, Text,
Tooltip, Tooltip,
useColorModeValue, useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { getChangeColor, getChangeBackgroundGradient, getChangeBorderColor } from '../../../../utils/colorUtils'; import { selectIsMobile } from '@store/slices/deviceSlice';
import { getChangeColor, getChangeBackgroundGradient, getChangeBorderColor } from '@utils/colorUtils';
/** /**
* 精简模式股票卡片组件 * 精简模式股票卡片组件
@@ -17,6 +19,7 @@ import { getChangeColor, getChangeBackgroundGradient, getChangeBorderColor } fro
* @param {Object} props.quote - 股票行情数据(可选) * @param {Object} props.quote - 股票行情数据(可选)
*/ */
const CompactStockItem = ({ stock, quote = null }) => { const CompactStockItem = ({ stock, quote = null }) => {
const isMobile = useSelector(selectIsMobile);
const nameColor = useColorModeValue('gray.700', 'gray.300'); const nameColor = useColorModeValue('gray.700', 'gray.300');
const handleViewDetail = () => { const handleViewDetail = () => {
@@ -45,10 +48,10 @@ const CompactStockItem = ({ stock, quote = null }) => {
> >
<Box <Box
bgGradient={getChangeBackgroundGradient(change)} bgGradient={getChangeBackgroundGradient(change)}
borderWidth="3px" borderWidth="1px"
borderColor={getChangeBorderColor(change)} borderColor={getChangeBorderColor(change)}
borderRadius="2xl" borderRadius="xl"
p={4} p={2}
onClick={handleViewDetail} onClick={handleViewDetail}
cursor="pointer" cursor="pointer"
boxShadow="lg" boxShadow="lg"
@@ -69,14 +72,14 @@ const CompactStockItem = ({ stock, quote = null }) => {
}} }}
transition="all 0.3s ease-in-out" transition="all 0.3s ease-in-out"
display="inline-block" display="inline-block"
minW="150px" minW="100px"
> >
{/* 股票代码 */} {/* 股票代码 */}
<Text <Text
fontSize="md" fontSize={isMobile ? "sm" : "md"}
fontWeight="bold" fontWeight="bold"
color={getChangeColor(change)} color={getChangeColor(change)}
mb={2} mb={isMobile ? 1 : 2}
textAlign="center" textAlign="center"
> >
{stock.stock_code} {stock.stock_code}
@@ -84,7 +87,7 @@ const CompactStockItem = ({ stock, quote = null }) => {
{/* 涨跌幅 - 超大号显示 */} {/* 涨跌幅 - 超大号显示 */}
<Text <Text
fontSize="3xl" fontSize={isMobile ? "xl" : "3xl"}
fontWeight="black" fontWeight="black"
color={getChangeColor(change)} color={getChangeColor(change)}
textAlign="center" textAlign="center"
@@ -96,9 +99,9 @@ const CompactStockItem = ({ stock, quote = null }) => {
{/* 股票名称(小字) */} {/* 股票名称(小字) */}
<Text <Text
fontSize="xs" fontSize={isMobile ? "2xs" : "xs"}
color={nameColor} color={nameColor}
mt={2} mt={isMobile ? 1 : 2}
textAlign="center" textAlign="center"
noOfLines={1} noOfLines={1}
fontWeight="medium" fontWeight="medium"

View File

@@ -418,7 +418,7 @@ const DynamicNewsDetailPanel = ({ event, showHeader = true }) => {
</CollapsibleSection> </CollapsibleSection>
{/* 讨论区(评论区) - 所有登录用户可用 */} {/* 讨论区(评论区) - 所有登录用户可用 */}
<Box mt={4}> <Box>
<EventCommentSection eventId={event.id} /> <EventCommentSection eventId={event.id} />
</Box> </Box>
</VStack> </VStack>

View File

@@ -37,16 +37,16 @@ const SimpleConceptCard = ({ concept, onClick, getRelevanceColor }) => {
return ( return (
<VStack <VStack
align="stretch" align="stretch"
spacing={2} spacing={1}
bg={cardBg} bg={cardBg}
borderWidth="1px" borderWidth="1px"
borderColor={borderColor} borderColor={borderColor}
borderRadius="md" borderRadius="md"
px={4} px={2}
py={2} py={1}
cursor="pointer" cursor="pointer"
transition="all 0.2s" transition="all 0.2s"
minW="200px" minW="100px"
_hover={{ _hover={{
transform: 'translateY(-1px)', transform: 'translateY(-1px)',
boxShadow: 'md', boxShadow: 'md',
@@ -68,17 +68,17 @@ const SimpleConceptCard = ({ concept, onClick, getRelevanceColor }) => {
</Text> </Text>
{/* 第二行:相关度 + 涨跌幅 */} {/* 第二行:相关度 + 涨跌幅 */}
<Flex justify="space-between" align="center" gap={2} flexWrap="wrap"> <Flex justify="space-between" align="center" gap={1} flexWrap="wrap">
{/* 相关度标签 */} {/* 相关度标签 */}
<Box <Box
bg={relevanceColors.bg} bg={relevanceColors.bg}
color={relevanceColors.color} color={relevanceColors.color}
px={2} px={1.5}
py={0.5} py={0.5}
borderRadius="sm" borderRadius="sm"
flexShrink={0} flexShrink={0}
> >
<Text fontSize="xs" fontWeight="medium" whiteSpace="nowrap"> <Text fontSize="10px" fontWeight="medium" whiteSpace="nowrap">
相关度: {relevanceScore}% 相关度: {relevanceScore}%
</Text> </Text>
</Box> </Box>
@@ -87,8 +87,8 @@ const SimpleConceptCard = ({ concept, onClick, getRelevanceColor }) => {
{changePct !== null && ( {changePct !== null && (
<Badge <Badge
colorScheme={changeColor} colorScheme={changeColor}
fontSize="xs" fontSize="10px"
px={2} px={1.5}
py={0.5} py={0.5}
flexShrink={0} flexShrink={0}
> >

View File

@@ -243,7 +243,7 @@ const RelatedConceptsSection = ({
} }
}} }}
> >
{isExpanded ? '收起' : '查看详细描述'} {isExpanded ? '收起' : '查看详细'}
</Button> </Button>
</Flex> </Flex>
{/* 第二行:交易日期信息 */} {/* 第二行:交易日期信息 */}

View File

@@ -2,6 +2,7 @@
// 股票卡片组件(融合表格功能的卡片样式) // 股票卡片组件(融合表格功能的卡片样式)
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { import {
Box, Box,
Flex, Flex,
@@ -16,13 +17,15 @@ import {
useColorModeValue, useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { StarIcon } from '@chakra-ui/icons'; import { StarIcon } from '@chakra-ui/icons';
import { Tag } from 'antd';
import { RobotOutlined } from '@ant-design/icons';
import { selectIsMobile } from '@store/slices/deviceSlice';
import MiniTimelineChart from '../StockDetailPanel/components/MiniTimelineChart'; import MiniTimelineChart from '../StockDetailPanel/components/MiniTimelineChart';
import MiniKLineChart from './MiniKLineChart'; import MiniKLineChart from './MiniKLineChart';
import TimelineChartModal from '../../../../components/StockChart/TimelineChartModal'; import TimelineChartModal from '@components/StockChart/TimelineChartModal';
import KLineChartModal from '../../../../components/StockChart/KLineChartModal'; import KLineChartModal from '@components/StockChart/KLineChartModal';
import CitedContent from '../../../../components/Citation/CitedContent'; import { getChangeColor } from '@utils/colorUtils';
import { getChangeColor } from '../../../../utils/colorUtils'; import { PROFESSIONAL_COLORS } from '@constants/professionalTheme';
import { PROFESSIONAL_COLORS } from '../../../../constants/professionalTheme';
/** /**
* 股票卡片组件 * 股票卡片组件
@@ -44,6 +47,7 @@ const StockListItem = ({
isInWatchlist = false, isInWatchlist = false,
onWatchlistToggle onWatchlistToggle
}) => { }) => {
const isMobile = useSelector(selectIsMobile);
const cardBg = PROFESSIONAL_COLORS.background.card; const cardBg = PROFESSIONAL_COLORS.background.card;
const borderColor = PROFESSIONAL_COLORS.border.default; const borderColor = PROFESSIONAL_COLORS.border.default;
const codeColor = '#3B82F6'; const codeColor = '#3B82F6';
@@ -128,9 +132,9 @@ const StockListItem = ({
transition="all 0.2s" transition="all 0.2s"
> >
{/* 单行紧凑布局:名称+涨跌幅 | 分时图 | K线图 | 关联描述 */} {/* 单行紧凑布局:名称+涨跌幅 | 分时图 | K线图 | 关联描述 */}
<HStack spacing={2} align="center" flexWrap="wrap"> <HStack spacing={2} align="center" flexWrap={isMobile ? 'wrap' : 'nowrap'}>
{/* 左侧:股票信息区 */} {/* 左侧:股票信息区 */}
<HStack spacing={2} minW="360px" maxW="380px" flexShrink={0}> <HStack spacing={2} overflow="hidden">
{/* 股票代码 + 名称 + 涨跌幅 */} {/* 股票代码 + 名称 + 涨跌幅 */}
<VStack <VStack
align="stretch" align="stretch"
@@ -194,24 +198,24 @@ const StockListItem = ({
</HStack> </HStack>
</VStack> </VStack>
{/* 分时图 - 更紧凑 */} {/* 分时图 - 自适应 */}
<VStack <VStack
w="115px" flex={1}
minW="80px"
maxW="150px"
borderWidth="1px" borderWidth="1px"
borderColor="rgba(59, 130, 246, 0.3)" borderColor="rgba(59, 130, 246, 0.3)"
borderRadius="md" borderRadius="md"
px={1.5} px={2}
py={1} py={1.5}
bg="rgba(59, 130, 246, 0.1)" bg="rgba(59, 130, 246, 0.1)"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setIsTimelineModalOpen(true); setIsTimelineModalOpen(true);
}} }}
cursor="pointer" cursor="pointer"
flexShrink={0}
align="stretch" align="stretch"
spacing={0} spacing={0}
h="fit-content"
_hover={{ _hover={{
borderColor: '#3B82F6', borderColor: '#3B82F6',
boxShadow: '0 0 10px rgba(59, 130, 246, 0.3)', boxShadow: '0 0 10px rgba(59, 130, 246, 0.3)',
@@ -228,7 +232,7 @@ const StockListItem = ({
> >
📈 分时 📈 分时
</Text> </Text>
<Box h="32px"> <Box h="28px">
<MiniTimelineChart <MiniTimelineChart
stockCode={stock.stock_code} stockCode={stock.stock_code}
eventTime={eventTime} eventTime={eventTime}
@@ -236,24 +240,24 @@ const StockListItem = ({
</Box> </Box>
</VStack> </VStack>
{/* K线图 - 更紧凑 */} {/* K线图 - 自适应 */}
<VStack <VStack
w="115px" flex={1}
minW="80px"
maxW="150px"
borderWidth="1px" borderWidth="1px"
borderColor="rgba(168, 85, 247, 0.3)" borderColor="rgba(168, 85, 247, 0.3)"
borderRadius="md" borderRadius="md"
px={1.5} px={2}
py={1} py={1.5}
bg="rgba(168, 85, 247, 0.1)" bg="rgba(168, 85, 247, 0.1)"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setIsKLineModalOpen(true); setIsKLineModalOpen(true);
}} }}
cursor="pointer" cursor="pointer"
flexShrink={0}
align="stretch" align="stretch"
spacing={0} spacing={0}
h="fit-content"
_hover={{ _hover={{
borderColor: '#A855F7', borderColor: '#A855F7',
boxShadow: '0 0 10px rgba(168, 85, 247, 0.3)', boxShadow: '0 0 10px rgba(168, 85, 247, 0.3)',
@@ -270,7 +274,7 @@ const StockListItem = ({
> >
📊 日线 📊 日线
</Text> </Text>
<Box h="32px"> <Box h="28px">
<MiniKLineChart <MiniKLineChart
stockCode={stock.stock_code} stockCode={stock.stock_code}
eventTime={eventTime} eventTime={eventTime}
@@ -281,7 +285,7 @@ const StockListItem = ({
{/* 关联描述 - 升级和降级处理 */} {/* 关联描述 - 升级和降级处理 */}
{stock.relation_desc && ( {stock.relation_desc && (
<Box flex={1} minW={0}> <Box flex={1} minW={0} flexBasis={isMobile ? '100%' : ''}>
{stock.relation_desc?.data ? ( {stock.relation_desc?.data ? (
// 升级:带引用来源的版本 - 添加折叠功能 // 升级:带引用来源的版本 - 添加折叠功能
<Tooltip <Tooltip
@@ -298,8 +302,6 @@ const StockListItem = ({
setIsDescExpanded(!isDescExpanded); setIsDescExpanded(!isDescExpanded);
}} }}
cursor="pointer" cursor="pointer"
px={3}
py={2}
bg={PROFESSIONAL_COLORS.background.secondary} bg={PROFESSIONAL_COLORS.background.secondary}
borderRadius="md" borderRadius="md"
_hover={{ _hover={{
@@ -308,18 +310,30 @@ const StockListItem = ({
transition="background 0.2s" transition="background 0.2s"
position="relative" position="relative"
> >
<Collapse in={isDescExpanded} startingHeight={40}> <Collapse in={isDescExpanded} startingHeight={56}>
<CitedContent {/* AI 标识 - 行内显示在文字前面 */}
data={stock.relation_desc} <Tag
title="" icon={<RobotOutlined />}
showAIBadge={true} color="purple"
textColor={PROFESSIONAL_COLORS.text.primary} style={{
containerStyle={{ fontSize: 12,
backgroundColor: 'transparent', padding: '2px 8px',
borderRadius: '0', marginRight: 8,
padding: '0', verticalAlign: 'middle',
display: 'inline-flex',
}} }}
/> >
AI合成
</Tag>
{/* 直接渲染文字内容 */}
<Text
as="span"
fontSize="sm"
color={PROFESSIONAL_COLORS.text.primary}
lineHeight="1.8"
>
{stock.relation_desc?.data?.map(item => item.sentences || item.query_part).filter(Boolean).join('')}
</Text>
</Collapse> </Collapse>
</Box> </Box>
</Tooltip> </Tooltip>
@@ -339,8 +353,6 @@ const StockListItem = ({
setIsDescExpanded(!isDescExpanded); setIsDescExpanded(!isDescExpanded);
}} }}
cursor="pointer" cursor="pointer"
px={3}
py={2}
bg={PROFESSIONAL_COLORS.background.secondary} bg={PROFESSIONAL_COLORS.background.secondary}
borderRadius="md" borderRadius="md"
_hover={{ _hover={{
@@ -350,7 +362,7 @@ const StockListItem = ({
position="relative" position="relative"
> >
{/* 去掉"关联描述"标题 */} {/* 去掉"关联描述"标题 */}
<Collapse in={isDescExpanded} startingHeight={36}> <Collapse in={isDescExpanded} startingHeight={56}>
<Text <Text
fontSize="xs" fontSize="xs"
color={nameColor} color={nameColor}

View File

@@ -32,10 +32,10 @@ const EventFollowButton = ({
size={size} size={size}
colorScheme="yellow" colorScheme="yellow"
variant="ghost" variant="ghost"
bg="whiteAlpha.500" bg="rgba(113, 128, 150, 0.6)"
boxShadow="sm" boxShadow="sm"
_hover={{ _hover={{
bg: 'whiteAlpha.800', bg: 'rgba(113, 128, 150, 0.8)',
boxShadow: 'md' boxShadow: 'md'
}} }}
icon={ icon={
@@ -47,8 +47,7 @@ const EventFollowButton = ({
) : ( ) : (
<AiOutlineStar <AiOutlineStar
size={iconSize} size={iconSize}
color="#718096" color="gold"
strokeWidth="1"
/> />
) )
} }

View File

@@ -0,0 +1,36 @@
.event-detail-modal {
top: 20% !important;
margin: 0 auto !important;
padding-bottom: 0 !important;
.ant-modal-content {
border-radius: 24px !important;
background: transparent;
}
// 标题样式 - 深色文字(白色背景)
.ant-modal-title {
color: #1A202C;
}
// 关闭按钮样式 - 深色(白色背景)
.ant-modal-close {
color: #4A5568;
&:hover {
color: #1A202C;
}
}
}
// 自底向上滑入动画
@keyframes slideUp {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { Modal } from 'antd';
import { selectIsMobile } from '@store/slices/deviceSlice';
import DynamicNewsDetailPanel from './DynamicNewsDetail/DynamicNewsDetailPanel';
import './EventDetailModal.less';
interface EventDetailModalProps {
/** 是否打开弹窗 */
open: boolean;
/** 关闭弹窗回调 */
onClose: () => void;
/** 事件对象 */
event: any; // TODO: 后续可替换为具体的 Event 类型
}
/**
* 事件详情弹窗组件
*/
const EventDetailModal: React.FC<EventDetailModalProps> = ({
open,
onClose,
event,
}) => {
const isMobile = useSelector(selectIsMobile);
return (
<Modal
open={open}
onCancel={onClose}
footer={null}
title={event?.title || '事件详情'}
width='100vw'
destroyOnClose
className="event-detail-modal"
styles={{
mask: { background: 'transparent' },
content: { borderRadius: 24, padding: 0, maxWidth: 1400, background: 'transparent', margin: '0 auto' },
header: { background: '#FFFFFF', borderBottom: '1px solid #E2E8F0', padding: '16px 24px', borderRadius: '24px 24px 0 0', margin: 0 },
body: { padding: 0 },
}}
>
{event && <DynamicNewsDetailPanel event={event} showHeader={false} />}
</Modal>
);
};
export default EventDetailModal;

View File

@@ -2,19 +2,11 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Card, Badge, Tag, Empty, Carousel, Tooltip } from 'antd'; import { Card, Badge, Tag, Empty, Carousel, Tooltip } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons'; import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
import { import { useDisclosure } from '@chakra-ui/react';
Modal, import EventDetailModal from './EventDetailModal';
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalCloseButton,
useDisclosure
} from '@chakra-ui/react';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import './HotEvents.css'; import './HotEvents.css';
import defaultEventImage from '../../../assets/img/default-event.jpg'; import defaultEventImage from '../../../assets/img/default-event.jpg';
import DynamicNewsDetailPanel from './DynamicNewsDetail';
// 自定义箭头组件 // 自定义箭头组件
const CustomArrow = ({ className, style, onClick, direction }) => { const CustomArrow = ({ className, style, onClick, direction }) => {
@@ -196,21 +188,12 @@ const HotEvents = ({ events, onPageChange, onEventClick }) => {
</Card> </Card>
)} )}
{/* 事件详情弹窗 - 使用 Chakra UI Modal与平铺模式一致 */} {/* 事件详情弹窗 */}
{isModalOpen ? ( <EventDetailModal
<Modal isOpen={isModalOpen} onClose={onModalClose} size="6xl" scrollBehavior="inside"> open={isModalOpen}
<ModalOverlay /> onClose={onModalClose}
<ModalContent> event={modalEvent}
<ModalHeader> />
{modalEvent?.title || '事件详情'}
</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
{modalEvent && <DynamicNewsDetailPanel event={modalEvent} />}
</ModalBody>
</ModalContent>
</Modal>
): null}
</div> </div>
); );
}; };

View File

@@ -29,10 +29,12 @@ import {
FaChartLine, FaChartLine,
FaInfoCircle FaInfoCircle
} from 'react-icons/fa'; } from 'react-icons/fa';
import { stockService } from '../../../services/eventService'; import { Tag } from 'antd';
import { logger } from '../../../utils/logger'; import { RobotOutlined } from '@ant-design/icons';
import CitedContent from '../../../components/Citation/CitedContent'; import { stockService } from '@services/eventService';
import { PROFESSIONAL_COLORS } from '../../../constants/professionalTheme'; import { logger } from '@utils/logger';
import CitedContent from '@components/Citation/CitedContent';
import { PROFESSIONAL_COLORS } from '@constants/professionalTheme';
const HistoricalEvents = ({ const HistoricalEvents = ({
events = [], events = [],
@@ -244,7 +246,7 @@ const HistoricalEvents = ({
key={event.id} key={event.id}
bg={cardBg} bg={cardBg}
borderWidth="1px" borderWidth="1px"
borderColor={borderColor} borderColor="gray.500"
borderRadius="lg" borderRadius="lg"
position="relative" position="relative"
overflow="visible" overflow="visible"
@@ -267,16 +269,16 @@ const HistoricalEvents = ({
}} }}
transition="all 0.2s" transition="all 0.2s"
> >
<VStack align="stretch" spacing={2} p={3}> <VStack align="stretch" spacing={3} p={4}>
{/* 顶部区域:左侧(标题+时间) + 右侧(按钮) */} {/* 顶部区域:左侧(标题+时间) + 右侧(按钮) */}
<HStack align="flex-start" spacing={3}> <HStack align="flex-start" spacing={3}>
{/* 左侧:标题 + 时间信息(允许折行) */} {/* 左侧:标题 + 时间信息(允许折行) */}
<VStack flex="1" align="flex-start" spacing={1}> <VStack flex="1" align="flex-start" spacing={2}>
{/* 标题 */} {/* 标题 */}
<Text <Text
fontSize="md" fontSize="lg"
fontWeight="bold" fontWeight="bold"
color={useColorModeValue('blue.600', 'blue.400')} color={useColorModeValue('blue.500', 'blue.300')}
lineHeight="1.4" lineHeight="1.4"
cursor="pointer" cursor="pointer"
onClick={(e) => { onClick={(e) => {
@@ -290,27 +292,28 @@ const HistoricalEvents = ({
{/* 时间 + Badges允许折行 */} {/* 时间 + Badges允许折行 */}
<HStack spacing={2} flexWrap="wrap"> <HStack spacing={2} flexWrap="wrap">
<Text fontSize="sm" color={textSecondary}> <Text fontSize="sm" color="gray.300" fontWeight="medium">
{formatDate(getEventDate(event))} {formatDate(getEventDate(event))}
</Text> </Text>
<Text fontSize="sm" color={textSecondary}> <Text fontSize="sm" color="gray.400">
({getRelativeTime(getEventDate(event))}) ({getRelativeTime(getEventDate(event))})
</Text> </Text>
{event.importance && ( {event.importance && (
<Badge colorScheme={importanceColor} size="sm"> <Badge colorScheme={importanceColor} fontSize="xs" px={2}>
重要性: {event.importance} 重要性: {event.importance}
</Badge> </Badge>
)} )}
{event.avg_change_pct !== undefined && event.avg_change_pct !== null && ( {event.avg_change_pct !== undefined && event.avg_change_pct !== null && (
<Badge <Badge
colorScheme={event.avg_change_pct > 0 ? 'red' : event.avg_change_pct < 0 ? 'green' : 'gray'} colorScheme={event.avg_change_pct > 0 ? 'red' : event.avg_change_pct < 0 ? 'green' : 'gray'}
size="sm" fontSize="xs"
px={2}
> >
涨幅: {event.avg_change_pct > 0 ? '+' : ''}{event.avg_change_pct.toFixed(2)}% 涨幅: {event.avg_change_pct > 0 ? '+' : ''}{event.avg_change_pct.toFixed(2)}%
</Badge> </Badge>
)} )}
{event.similarity !== undefined && event.similarity !== null && ( {event.similarity !== undefined && event.similarity !== null && (
<Badge colorScheme={getSimilarityColor(event.similarity)} size="sm"> <Badge colorScheme={getSimilarityColor(event.similarity)} fontSize="xs" px={2}>
相关度: {event.similarity} 相关度: {event.similarity}
</Badge> </Badge>
)} )}
@@ -344,10 +347,9 @@ const HistoricalEvents = ({
data={content} data={content}
title="" title=""
showAIBadge={true} showAIBadge={true}
textColor={PROFESSIONAL_COLORS.text.primary} textColor="#E2E8F0"
containerStyle={{ containerStyle={{
backgroundColor: useColorModeValue('#f7fafc', 'rgba(45, 55, 72, 0.6)'), backgroundColor: 'transparent',
borderRadius: '8px',
padding: '0', padding: '0',
}} }}
/> />

View File

@@ -28,9 +28,11 @@ import {
ModalCloseButton, ModalCloseButton,
Icon, Icon,
useColorModeValue, useColorModeValue,
Tooltip Tooltip,
Center
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { InfoIcon, ViewIcon } from '@chakra-ui/icons'; import { InfoIcon, ViewIcon } from '@chakra-ui/icons';
import { Share2, GitBranch, Inbox } from 'lucide-react';
import ReactECharts from 'echarts-for-react'; import ReactECharts from 'echarts-for-react';
import { eventService } from '../../../services/eventService'; import { eventService } from '../../../services/eventService';
import CitedContent from '../../../components/Citation/CitedContent'; import CitedContent from '../../../components/Citation/CitedContent';
@@ -637,7 +639,7 @@ const TransmissionChainAnalysis = ({ eventId }) => {
}; };
return ( return (
<Box p={6}> <Box>
{/* 统计信息条 */} {/* 统计信息条 */}
<Box <Box
mb={4} mb={4}
@@ -647,56 +649,57 @@ const TransmissionChainAnalysis = ({ eventId }) => {
borderColor={PROFESSIONAL_COLORS.border.default} borderColor={PROFESSIONAL_COLORS.border.default}
bg={PROFESSIONAL_COLORS.background.secondary} bg={PROFESSIONAL_COLORS.background.secondary}
> >
<HStack spacing={6} wrap="wrap"> <Flex wrap="wrap" gap={{ base: 3, md: 6 }}>
<Stat> <Stat minW="fit-content">
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>总节点数</StatLabel> <StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">总节点数</StatLabel>
<StatNumber color={PROFESSIONAL_COLORS.text.primary}>{stats.totalNodes}</StatNumber> <StatNumber fontSize={{ base: "xl", md: "2xl" }} color={PROFESSIONAL_COLORS.text.primary}>{stats.totalNodes}</StatNumber>
</Stat> </Stat>
<Stat> <Stat minW="fit-content">
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>涉及行业</StatLabel> <StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">涉及行业</StatLabel>
<StatNumber color={PROFESSIONAL_COLORS.text.primary}>{stats.involvedIndustries}</StatNumber> <StatNumber fontSize={{ base: "xl", md: "2xl" }} color={PROFESSIONAL_COLORS.text.primary}>{stats.involvedIndustries}</StatNumber>
</Stat> </Stat>
<Stat> <Stat minW="fit-content">
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>相关公司</StatLabel> <StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">相关公司</StatLabel>
<StatNumber color={PROFESSIONAL_COLORS.text.primary}>{stats.relatedCompanies}</StatNumber> <StatNumber fontSize={{ base: "xl", md: "2xl" }} color={PROFESSIONAL_COLORS.text.primary}>{stats.relatedCompanies}</StatNumber>
</Stat> </Stat>
<Stat> <Stat minW="fit-content">
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>正向影响</StatLabel> <StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">正向影响</StatLabel>
<StatNumber color="#10B981">{stats.positiveImpact}</StatNumber> <StatNumber fontSize={{ base: "xl", md: "2xl" }} color="#10B981">{stats.positiveImpact}</StatNumber>
</Stat> </Stat>
<Stat> <Stat minW="fit-content">
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>负向影响</StatLabel> <StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">负向影响</StatLabel>
<StatNumber color="#EF4444">{stats.negativeImpact}</StatNumber> <StatNumber fontSize={{ base: "xl", md: "2xl" }} color="#EF4444">{stats.negativeImpact}</StatNumber>
</Stat> </Stat>
<Stat> <Stat minW="fit-content">
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>循环效应</StatLabel> <StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">循环效应</StatLabel>
<StatNumber color="#A855F7">{stats.circularEffect}</StatNumber> <StatNumber fontSize={{ base: "xl", md: "2xl" }} color="#A855F7">{stats.circularEffect}</StatNumber>
</Stat> </Stat>
</HStack> </Flex>
</Box> </Box>
{/* 自定义图例 */} {/* 自定义图例 */}
<Box mb={4}> <Flex mb={4} wrap="wrap" gap={2}>
<HStack spacing={4} wrap="wrap">
{Object.entries(NODE_STYLES).map(([type, style]) => ( {Object.entries(NODE_STYLES).map(([type, style]) => (
<Tag <Tag
key={type} key={type}
size="md" size="sm"
px={2}
py={1}
bg={PROFESSIONAL_COLORS.background.secondary} bg={PROFESSIONAL_COLORS.background.secondary}
color={PROFESSIONAL_COLORS.text.primary} color={PROFESSIONAL_COLORS.text.primary}
borderWidth="1px" borderWidth="1px"
borderColor={PROFESSIONAL_COLORS.border.default} borderColor={PROFESSIONAL_COLORS.border.default}
> >
<Box w={3} h={3} bg={style.color} borderRadius="sm" mr={2} /> <Box w={2.5} h={2.5} bg={style.color} borderRadius="sm" mr={1.5} />
{NODE_TYPE_LABELS[type] || type} {NODE_TYPE_LABELS[type] || type}
</Tag> </Tag>
))} ))}
</HStack> </Flex>
</Box>
{/* 视图切换按钮 */} {/* 视图切换按钮 */}
<Flex mb={4} gap={2}> <Flex mb={4} gap={2}>
<Button <Button
leftIcon={<Icon as={Share2} boxSize={4} />}
bg={viewMode === 'graph' ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.background.secondary} bg={viewMode === 'graph' ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.background.secondary}
color={viewMode === 'graph' ? 'black' : PROFESSIONAL_COLORS.text.primary} color={viewMode === 'graph' ? 'black' : PROFESSIONAL_COLORS.text.primary}
_hover={{ _hover={{
@@ -710,6 +713,7 @@ const TransmissionChainAnalysis = ({ eventId }) => {
力导向图 力导向图
</Button> </Button>
<Button <Button
leftIcon={<Icon as={GitBranch} boxSize={4} />}
bg={viewMode === 'sankey' ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.background.secondary} bg={viewMode === 'sankey' ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.background.secondary}
color={viewMode === 'sankey' ? 'black' : PROFESSIONAL_COLORS.text.primary} color={viewMode === 'sankey' ? 'black' : PROFESSIONAL_COLORS.text.primary}
_hover={{ _hover={{
@@ -722,7 +726,6 @@ const TransmissionChainAnalysis = ({ eventId }) => {
> >
桑基图 桑基图
</Button> </Button>
</Flex> </Flex>
{loading && ( {loading && (
@@ -748,35 +751,54 @@ const TransmissionChainAnalysis = ({ eventId }) => {
{!loading && !error && ( {!loading && !error && (
<Box> <Box>
{/* 提示信息 */} {/* 图表容器 - 宽高比 2:1H5 自适应 */}
<Alert
status="info"
mb={4}
borderRadius="md"
bg="rgba(59, 130, 246, 0.1)"
color="#3B82F6"
borderWidth="1px"
borderColor="#3B82F6"
>
<AlertIcon />
<Text fontSize="sm" color={PROFESSIONAL_COLORS.text.secondary}>
<Icon as={ViewIcon} mr={2} />
点击图表中的节点可以查看详细信息
</Text>
</Alert>
{/* 图表容器 */}
<Box <Box
h={viewMode === 'sankey' ? "600px" : "700px"} position="relative"
w="100%"
pb={{ base: "75%", md: "50%" }}
border="1px solid" border="1px solid"
borderColor={PROFESSIONAL_COLORS.border.default} borderColor={PROFESSIONAL_COLORS.border.default}
borderRadius="lg" borderRadius="lg"
boxShadow="0 4px 12px rgba(0, 0, 0, 0.3)" boxShadow="0 4px 12px rgba(0, 0, 0, 0.3)"
bg={PROFESSIONAL_COLORS.background.card} bg={PROFESSIONAL_COLORS.background.card}
p={4}
ref={containerRef} ref={containerRef}
> >
<Box
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
p={4}
>
{/* 提示信息 - 固定在左上角 */}
<Text
position="absolute"
top={2}
left={3}
fontSize="xs"
color={PROFESSIONAL_COLORS.text.muted}
zIndex={1}
bg="rgba(0, 0, 0, 0.5)"
px={2}
py={1}
borderRadius="md"
>
<Icon as={ViewIcon} mr={1} boxSize={3} />
点击节点查看详情
</Text>
{chartReady && ( {chartReady && (
<>
{/* 空状态提示 */}
{(viewMode === 'graph' && (!graphData || !graphData.nodes || graphData.nodes.length === 0)) ||
(viewMode === 'sankey' && (!sankeyData || !sankeyData.nodes || sankeyData.nodes.length === 0)) ? (
<Center h="100%" flexDirection="column">
<Icon as={Inbox} boxSize={12} color={PROFESSIONAL_COLORS.text.muted} />
<Text mt={4} color={PROFESSIONAL_COLORS.text.muted} fontSize="sm">
暂无传导链数据
</Text>
</Center>
) : (
<> <>
{viewMode === 'graph' ? ( {viewMode === 'graph' ? (
<ReactECharts <ReactECharts
@@ -817,6 +839,9 @@ const TransmissionChainAnalysis = ({ eventId }) => {
)} )}
</> </>
)} )}
</>
)}
</Box>
</Box> </Box>
</Box> </Box>
)} )}
@@ -826,8 +851,8 @@ const TransmissionChainAnalysis = ({ eventId }) => {
<Modal isOpen={isModalOpen} onClose={handleCloseModal} size="xl"> <Modal isOpen={isModalOpen} onClose={handleCloseModal} size="xl">
<ModalOverlay /> <ModalOverlay />
<ModalContent maxH="80vh" bg={modalBgColor}> <ModalContent maxH="80vh" bg={modalBgColor}>
<ModalHeader borderBottom="1px solid" borderColor={modalBorderColor}> <ModalHeader borderBottom="1px solid" borderColor={modalBorderColor} pr={12}>
<HStack justify="space-between"> <HStack justify="space-between" pr={2}>
<Text color={PROFESSIONAL_COLORS.text.primary}>{selectedNode ? '节点详情' : '传导链分析'}</Text> <Text color={PROFESSIONAL_COLORS.text.primary}>{selectedNode ? '节点详情' : '传导链分析'}</Text>
{selectedNode && ( {selectedNode && (
<Badge <Badge
@@ -841,7 +866,10 @@ const TransmissionChainAnalysis = ({ eventId }) => {
)} )}
</HStack> </HStack>
</ModalHeader> </ModalHeader>
<ModalCloseButton /> <ModalCloseButton
color={PROFESSIONAL_COLORS.text.secondary}
_hover={{ bg: 'rgba(255, 255, 255, 0.1)' }}
/>
<ModalBody overflowY="auto"> <ModalBody overflowY="auto">
{selectedNode ? ( {selectedNode ? (
@@ -1084,11 +1112,15 @@ const TransmissionChainAnalysis = ({ eventId }) => {
prefix="机制:" prefix="机制:"
prefixStyle={{ fontSize: 12, color: PROFESSIONAL_COLORS.text.secondary, fontWeight: 'bold' }} prefixStyle={{ fontSize: 12, color: PROFESSIONAL_COLORS.text.secondary, fontWeight: 'bold' }}
textColor={PROFESSIONAL_COLORS.text.primary} textColor={PROFESSIONAL_COLORS.text.primary}
containerStyle={{ marginTop: 8 }} containerStyle={{
marginTop: 8,
backgroundColor: 'transparent',
padding: 0,
}}
showAIBadge={false} showAIBadge={false}
/> />
) : parent.transmission_mechanism ? ( ) : parent.transmission_mechanism ? (
<Text fontSize="xs" color="gray.600"> <Text fontSize="xs" color={PROFESSIONAL_COLORS.text.secondary}>
机制: {parent.transmission_mechanism}AI合成 机制: {parent.transmission_mechanism}AI合成
</Text> </Text>
) : null} ) : null}
@@ -1109,19 +1141,38 @@ const TransmissionChainAnalysis = ({ eventId }) => {
if (targetsFromAPI) { if (targetsFromAPI) {
return ( return (
<Box> <Box>
<Text fontWeight="bold" mb={2} color="blue.600"> <Text fontWeight="bold" mb={2} color={PROFESSIONAL_COLORS.gold[500]}>
影响输出 ({nodeDetail.children.length}) 影响输出 ({nodeDetail.children.length})AI合成
</Text> </Text>
<List spacing={2}> <List spacing={2}>
{nodeDetail.children.map((child, index) => ( {nodeDetail.children.map((child, index) => (
<ListItem key={index} p={2} bg="gray.50" borderRadius="md" borderLeft="3px solid" borderColor="orange.300" position="relative"> <ListItem
key={index}
p={2}
bg={PROFESSIONAL_COLORS.background.secondary}
borderRadius="md"
borderLeft="3px solid"
borderColor="#FB923C"
position="relative"
>
{child.direction && ( {child.direction && (
<Box position="absolute" top={2} right={2} zIndex={1}> <Box position="absolute" top={2} right={2} zIndex={1}>
<Badge <Badge
colorScheme={ bg={
child.direction === 'positive' ? 'green' : child.direction === 'positive' ? 'rgba(16, 185, 129, 0.15)' :
child.direction === 'negative' ? 'red' : child.direction === 'negative' ? 'rgba(239, 68, 68, 0.15)' :
'gray' 'rgba(107, 114, 128, 0.15)'
}
color={
child.direction === 'positive' ? '#10B981' :
child.direction === 'negative' ? '#EF4444' :
'#6B7280'
}
borderWidth="1px"
borderColor={
child.direction === 'positive' ? '#10B981' :
child.direction === 'negative' ? '#EF4444' :
'#6B7280'
} }
size="sm" size="sm"
> >
@@ -1132,7 +1183,7 @@ const TransmissionChainAnalysis = ({ eventId }) => {
</Box> </Box>
)} )}
<VStack align="stretch" spacing={1}> <VStack align="stretch" spacing={1}>
<Text fontWeight="bold" fontSize="sm" pr={child.direction ? 20 : 0}>{child.name}</Text> <Text fontWeight="bold" fontSize="sm" color={PROFESSIONAL_COLORS.text.primary} pr={child.direction ? 20 : 0}>{child.name}</Text>
{child.transmission_mechanism?.data ? ( {child.transmission_mechanism?.data ? (
<CitedContent <CitedContent
data={child.transmission_mechanism} data={child.transmission_mechanism}
@@ -1140,11 +1191,15 @@ const TransmissionChainAnalysis = ({ eventId }) => {
prefix="机制:" prefix="机制:"
prefixStyle={{ fontSize: 12, color: PROFESSIONAL_COLORS.text.secondary, fontWeight: 'bold' }} prefixStyle={{ fontSize: 12, color: PROFESSIONAL_COLORS.text.secondary, fontWeight: 'bold' }}
textColor={PROFESSIONAL_COLORS.text.primary} textColor={PROFESSIONAL_COLORS.text.primary}
containerStyle={{ marginTop: 8 }} containerStyle={{
marginTop: 8,
backgroundColor: 'transparent',
padding: 0,
}}
showAIBadge={false} showAIBadge={false}
/> />
) : child.transmission_mechanism ? ( ) : child.transmission_mechanism ? (
<Text fontSize="xs" color="gray.600"> <Text fontSize="xs" color={PROFESSIONAL_COLORS.text.secondary}>
机制: {child.transmission_mechanism}AI合成 机制: {child.transmission_mechanism}AI合成
</Text> </Text>
) : null} ) : null}
@@ -1169,7 +1224,14 @@ const TransmissionChainAnalysis = ({ eventId }) => {
</ModalBody> </ModalBody>
<ModalFooter borderTop="1px solid" borderColor={modalBorderColor}> <ModalFooter borderTop="1px solid" borderColor={modalBorderColor}>
<Button onClick={handleCloseModal}>关闭</Button> <Button
onClick={handleCloseModal}
variant="ghost"
color={PROFESSIONAL_COLORS.text.secondary}
_hover={{ bg: 'rgba(255, 255, 255, 0.1)' }}
>
关闭
</Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
</Modal> </Modal>