update pay ui
This commit is contained in:
@@ -26,15 +26,22 @@ import { FaTable } from 'react-icons/fa';
|
||||
import marketService from '@services/marketService';
|
||||
import { logger } from '@utils/logger';
|
||||
|
||||
// 股票信息类型
|
||||
// 股票信息类型 - 兼容新旧API格式
|
||||
interface StockInfo {
|
||||
stock_code: string;
|
||||
stock_name: string;
|
||||
stock_code?: string;
|
||||
stock_name?: string;
|
||||
code?: string; // 新API格式
|
||||
name?: string; // 新API格式
|
||||
reason?: string;
|
||||
change_pct?: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// 获取股票代码(兼容新旧API)
|
||||
const getStockCode = (stock: StockInfo): string => stock.code || stock.stock_code || '';
|
||||
// 获取股票名称(兼容新旧API)
|
||||
const getStockName = (stock: StockInfo): string => stock.name || stock.stock_name || '未知';
|
||||
|
||||
// 概念信息类型
|
||||
export interface ConceptInfo {
|
||||
concept_id?: string;
|
||||
@@ -69,9 +76,12 @@ const ConceptStocksModal: React.FC<ConceptStocksModalProps> = ({
|
||||
const [stockMarketData, setStockMarketData] = useState<Record<string, MarketData>>({});
|
||||
const [loadingStockData, setLoadingStockData] = useState(false);
|
||||
|
||||
// 颜色主题
|
||||
const cardBg = useColorModeValue('white', '#1a1a1a');
|
||||
const hoverBg = useColorModeValue('gray.50', '#2a2a2a');
|
||||
// 深色主题颜色
|
||||
const cardBg = 'rgba(15, 23, 42, 0.95)';
|
||||
const hoverBg = 'whiteAlpha.100';
|
||||
const textColor = 'white';
|
||||
const subTextColor = 'whiteAlpha.700';
|
||||
const borderColor = 'whiteAlpha.100';
|
||||
|
||||
// 响应式配置 - 添加 fallback 避免首次渲染时返回 undefined 导致弹窗异常
|
||||
const isMobile = useBreakpointValue({ base: true, md: false }, { fallback: 'md' });
|
||||
@@ -91,13 +101,14 @@ const ConceptStocksModal: React.FC<ConceptStocksModalProps> = ({
|
||||
for (let i = 0; i < stocks.length; i += batchSize) {
|
||||
const batch = stocks.slice(i, i + batchSize);
|
||||
const promises = batch.map(async (stock) => {
|
||||
if (!stock.stock_code) return null;
|
||||
const seccode = stock.stock_code.substring(0, 6);
|
||||
const stockCode = getStockCode(stock);
|
||||
if (!stockCode) return null;
|
||||
const seccode = stockCode.substring(0, 6);
|
||||
try {
|
||||
const response = await marketService.getTradeData(seccode, 1);
|
||||
if (response.success && response.data?.length > 0) {
|
||||
const latestData = response.data[response.data.length - 1];
|
||||
return { stock_code: stock.stock_code, ...latestData };
|
||||
return { stock_code: stockCode, ...latestData };
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('ConceptStocksModal', '获取股票行情失败', { stockCode: seccode });
|
||||
@@ -168,16 +179,18 @@ const ConceptStocksModal: React.FC<ConceptStocksModalProps> = ({
|
||||
<Table variant="simple" size="sm" minW={isMobile ? '600px' : undefined}>
|
||||
<Thead position="sticky" top={0} bg={cardBg} zIndex={1}>
|
||||
<Tr>
|
||||
<Th whiteSpace="nowrap">股票名称</Th>
|
||||
<Th whiteSpace="nowrap">股票代码</Th>
|
||||
<Th isNumeric whiteSpace="nowrap">现价</Th>
|
||||
<Th isNumeric whiteSpace="nowrap">当日涨跌幅</Th>
|
||||
<Th whiteSpace="nowrap" minW="200px">板块原因</Th>
|
||||
<Th whiteSpace="nowrap" color={subTextColor} borderColor={borderColor}>股票名称</Th>
|
||||
<Th whiteSpace="nowrap" color={subTextColor} borderColor={borderColor}>股票代码</Th>
|
||||
<Th isNumeric whiteSpace="nowrap" color={subTextColor} borderColor={borderColor}>现价</Th>
|
||||
<Th isNumeric whiteSpace="nowrap" color={subTextColor} borderColor={borderColor}>当日涨跌幅</Th>
|
||||
<Th whiteSpace="nowrap" minW="200px" color={subTextColor} borderColor={borderColor}>板块原因</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{stocks.map((stock, idx) => {
|
||||
const marketData = stockMarketData[stock.stock_code];
|
||||
const stockCode = getStockCode(stock);
|
||||
const stockName = getStockName(stock);
|
||||
const marketData = stockMarketData[stockCode];
|
||||
const changePercent = marketData?.change_percent;
|
||||
|
||||
return (
|
||||
@@ -185,15 +198,15 @@ const ConceptStocksModal: React.FC<ConceptStocksModalProps> = ({
|
||||
key={idx}
|
||||
_hover={{ bg: hoverBg }}
|
||||
cursor="pointer"
|
||||
onClick={() => handleStockClick(stock.stock_code)}
|
||||
onClick={() => handleStockClick(stockCode)}
|
||||
>
|
||||
<Td color="blue.500" fontWeight="medium">
|
||||
{stock.stock_name}
|
||||
<Td color="cyan.400" fontWeight="medium" borderColor={borderColor}>
|
||||
{stockName}
|
||||
</Td>
|
||||
<Td>{stock.stock_code}</Td>
|
||||
<Td isNumeric>
|
||||
<Td color={subTextColor} borderColor={borderColor}>{stockCode}</Td>
|
||||
<Td isNumeric color={textColor} borderColor={borderColor}>
|
||||
{loadingStockData ? (
|
||||
<Spinner size="xs" />
|
||||
<Spinner size="xs" color="purple.400" />
|
||||
) : marketData?.close ? (
|
||||
`¥${marketData.close.toFixed(2)}`
|
||||
) : (
|
||||
@@ -203,23 +216,24 @@ const ConceptStocksModal: React.FC<ConceptStocksModalProps> = ({
|
||||
<Td
|
||||
isNumeric
|
||||
fontWeight="bold"
|
||||
borderColor={borderColor}
|
||||
color={
|
||||
changePercent && changePercent > 0
|
||||
? 'red.500'
|
||||
? 'red.400'
|
||||
: changePercent && changePercent < 0
|
||||
? 'green.500'
|
||||
: 'gray.500'
|
||||
? 'green.400'
|
||||
: 'whiteAlpha.500'
|
||||
}
|
||||
>
|
||||
{loadingStockData ? (
|
||||
<Spinner size="xs" />
|
||||
<Spinner size="xs" color="purple.400" />
|
||||
) : changePercent !== undefined ? (
|
||||
`${changePercent > 0 ? '+' : ''}${changePercent.toFixed(2)}%`
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Td>
|
||||
<Td fontSize="xs" color="gray.600" maxW="300px">
|
||||
<Td fontSize="xs" color={subTextColor} maxW="300px" borderColor={borderColor}>
|
||||
<Text noOfLines={2}>{stock.reason || '-'}</Text>
|
||||
</Td>
|
||||
</Tr>
|
||||
|
||||
@@ -608,10 +608,20 @@ const ConceptTimelineModal = ({
|
||||
scrollBehavior="inside"
|
||||
isCentered
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent maxW="1400px" m={{ base: 0, md: 'auto' }} mx="auto">
|
||||
<ModalOverlay bg="blackAlpha.800" backdropFilter="blur(8px)" />
|
||||
<ModalContent
|
||||
maxW="1400px"
|
||||
m={{ base: 0, md: 'auto' }}
|
||||
mx="auto"
|
||||
bg="rgba(15, 23, 42, 0.95)"
|
||||
backdropFilter="blur(20px)"
|
||||
border="1px solid"
|
||||
borderColor="whiteAlpha.100"
|
||||
>
|
||||
<ModalHeader
|
||||
bgGradient="linear(135deg, purple.600 0%, purple.500 50%, pink.500 100%)"
|
||||
bg="rgba(15, 23, 42, 0.98)"
|
||||
borderBottom="1px solid"
|
||||
borderColor="whiteAlpha.100"
|
||||
color="white"
|
||||
position="sticky"
|
||||
top={0}
|
||||
@@ -624,35 +634,34 @@ const ConceptTimelineModal = ({
|
||||
<Icon
|
||||
as={FaChartLine}
|
||||
boxSize={{ base: 4, md: 6 }}
|
||||
filter="drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
|
||||
color="cyan.400"
|
||||
/>
|
||||
<Text
|
||||
fontSize={{ base: 'md', md: 'xl' }}
|
||||
fontWeight="bold"
|
||||
textShadow="0 2px 4px rgba(0,0,0,0.2)"
|
||||
color="white"
|
||||
noOfLines={1}
|
||||
maxW={{ base: '120px', md: 'none' }}
|
||||
>
|
||||
{conceptName} - 历史时间轴
|
||||
</Text>
|
||||
<Badge
|
||||
colorScheme="yellow"
|
||||
px={{ base: 2, md: 3 }}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
boxShadow="md"
|
||||
>
|
||||
最近100天
|
||||
</Badge>
|
||||
<Badge
|
||||
bg="whiteAlpha.300"
|
||||
bg="purple.500"
|
||||
color="white"
|
||||
px={{ base: 2, md: 3 }}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
>
|
||||
最近100天
|
||||
</Badge>
|
||||
<Badge
|
||||
bg="whiteAlpha.100"
|
||||
color="whiteAlpha.800"
|
||||
px={{ base: 2, md: 3 }}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
fontSize="xs"
|
||||
backdropFilter="blur(10px)"
|
||||
display={{ base: 'none', sm: 'flex' }}
|
||||
>
|
||||
🔥 Max版功能
|
||||
@@ -671,29 +680,29 @@ const ConceptTimelineModal = ({
|
||||
<ModalBody
|
||||
py={{ base: 2, md: 6 }}
|
||||
px={{ base: 0, md: 6 }}
|
||||
bg="gray.50"
|
||||
bg="transparent"
|
||||
css={{
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '8px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
background: '#f1f1f1',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
borderRadius: '10px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: '#c1c1c1',
|
||||
background: 'rgba(255,255,255,0.2)',
|
||||
borderRadius: '10px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
background: '#a8a8a8',
|
||||
background: 'rgba(255,255,255,0.3)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<Center py={20}>
|
||||
<VStack spacing={4}>
|
||||
<Spinner size="xl" color="purple.500" thickness="4px" />
|
||||
<Text color="gray.600">正在加载时间轴数据...</Text>
|
||||
<Spinner size="xl" color="cyan.400" thickness="4px" />
|
||||
<Text color="whiteAlpha.700">正在加载时间轴数据...</Text>
|
||||
</VStack>
|
||||
</Center>
|
||||
) : timelineData.length > 0 ? (
|
||||
@@ -715,83 +724,79 @@ const ConceptTimelineModal = ({
|
||||
spacing={{ base: 1, md: 2 }}
|
||||
px={{ base: 2, md: 4 }}
|
||||
py={{ base: 1, md: 2 }}
|
||||
bg="purple.50"
|
||||
bg="whiteAlpha.100"
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor="purple.200"
|
||||
boxShadow="sm"
|
||||
borderColor="purple.500"
|
||||
flexShrink={0}
|
||||
>
|
||||
<Box w={{ base: 2, md: 3 }} h={{ base: 2, md: 3 }} bg="#9F7AEA" borderRadius="full" />
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="gray.700" whiteSpace="nowrap">📰 新闻</Text>
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="whiteAlpha.900" whiteSpace="nowrap">📰 新闻</Text>
|
||||
</HStack>
|
||||
<HStack
|
||||
spacing={{ base: 1, md: 2 }}
|
||||
px={{ base: 2, md: 4 }}
|
||||
py={{ base: 1, md: 2 }}
|
||||
bg="purple.50"
|
||||
bg="whiteAlpha.100"
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor="purple.300"
|
||||
boxShadow="sm"
|
||||
borderColor="purple.600"
|
||||
flexShrink={0}
|
||||
>
|
||||
<Box w={{ base: 2, md: 3 }} h={{ base: 2, md: 3 }} bg="#805AD5" borderRadius="full" />
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="gray.700" whiteSpace="nowrap">📊 研报</Text>
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="whiteAlpha.900" whiteSpace="nowrap">📊 研报</Text>
|
||||
</HStack>
|
||||
<HStack
|
||||
spacing={{ base: 1, md: 2 }}
|
||||
px={{ base: 2, md: 4 }}
|
||||
py={{ base: 1, md: 2 }}
|
||||
bg="red.50"
|
||||
bg="whiteAlpha.100"
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor="red.200"
|
||||
boxShadow="sm"
|
||||
borderColor="red.400"
|
||||
flexShrink={0}
|
||||
>
|
||||
<Icon as={FaArrowUp} color="red.500" boxSize={{ base: 2, md: 3 }} />
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="gray.700" whiteSpace="nowrap">上涨</Text>
|
||||
<Icon as={FaArrowUp} color="red.400" boxSize={{ base: 2, md: 3 }} />
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="whiteAlpha.900" whiteSpace="nowrap">上涨</Text>
|
||||
</HStack>
|
||||
<HStack
|
||||
spacing={{ base: 1, md: 2 }}
|
||||
px={{ base: 2, md: 4 }}
|
||||
py={{ base: 1, md: 2 }}
|
||||
bg="green.50"
|
||||
bg="whiteAlpha.100"
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor="green.200"
|
||||
boxShadow="sm"
|
||||
borderColor="green.400"
|
||||
flexShrink={0}
|
||||
>
|
||||
<Icon as={FaArrowDown} color="green.500" boxSize={{ base: 2, md: 3 }} />
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="gray.700" whiteSpace="nowrap">下跌</Text>
|
||||
<Icon as={FaArrowDown} color="green.400" boxSize={{ base: 2, md: 3 }} />
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="whiteAlpha.900" whiteSpace="nowrap">下跌</Text>
|
||||
</HStack>
|
||||
<HStack
|
||||
spacing={{ base: 1, md: 2 }}
|
||||
px={{ base: 2, md: 4 }}
|
||||
py={{ base: 1, md: 2 }}
|
||||
bg="orange.50"
|
||||
bg="whiteAlpha.100"
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor="orange.200"
|
||||
boxShadow="sm"
|
||||
borderColor="orange.400"
|
||||
flexShrink={0}
|
||||
>
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="bold">🔥</Text>
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="gray.700" whiteSpace="nowrap">涨3%+</Text>
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="medium" color="whiteAlpha.900" whiteSpace="nowrap">涨3%+</Text>
|
||||
</HStack>
|
||||
</Flex>
|
||||
|
||||
{/* FullCalendar 日历组件 */}
|
||||
<Box
|
||||
height={{ base: '500px', md: '700px' }}
|
||||
bg="white"
|
||||
bg="rgba(15, 23, 42, 0.6)"
|
||||
borderRadius={{ base: 'none', md: 'xl' }}
|
||||
boxShadow={{ base: 'none', md: 'lg' }}
|
||||
border="1px solid"
|
||||
borderColor="whiteAlpha.100"
|
||||
p={{ base: 1, md: 4 }}
|
||||
sx={{
|
||||
// FullCalendar 样式定制
|
||||
// FullCalendar 深色主题样式定制
|
||||
'.fc': {
|
||||
height: '100%',
|
||||
},
|
||||
@@ -808,42 +813,61 @@ const ConceptTimelineModal = ({
|
||||
'.fc-toolbar-title': {
|
||||
fontSize: { base: '1rem', md: '1.5rem' },
|
||||
fontWeight: 'bold',
|
||||
color: 'purple.600',
|
||||
color: 'white',
|
||||
},
|
||||
'.fc-button': {
|
||||
backgroundColor: '#9F7AEA',
|
||||
borderColor: '#9F7AEA',
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.6)',
|
||||
borderColor: 'rgba(139, 92, 246, 0.8)',
|
||||
color: 'white',
|
||||
padding: { base: '4px 8px', md: '6px 12px' },
|
||||
fontSize: { base: '12px', md: '14px' },
|
||||
'&:hover': {
|
||||
backgroundColor: '#805AD5',
|
||||
borderColor: '#805AD5',
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.8)',
|
||||
borderColor: 'rgba(139, 92, 246, 1)',
|
||||
},
|
||||
'&:active, &:focus': {
|
||||
backgroundColor: '#6B46C1',
|
||||
borderColor: '#6B46C1',
|
||||
backgroundColor: 'rgba(139, 92, 246, 1)',
|
||||
borderColor: 'rgba(139, 92, 246, 1)',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
},
|
||||
'.fc-button-active': {
|
||||
backgroundColor: '#6B46C1',
|
||||
borderColor: '#6B46C1',
|
||||
backgroundColor: 'rgba(139, 92, 246, 1)',
|
||||
borderColor: 'rgba(139, 92, 246, 1)',
|
||||
},
|
||||
// 深色主题 - 表格边框和背景
|
||||
'.fc-theme-standard td, .fc-theme-standard th': {
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
'.fc-theme-standard .fc-scrollgrid': {
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
'.fc-col-header-cell': {
|
||||
backgroundColor: 'rgba(15, 23, 42, 0.8)',
|
||||
},
|
||||
'.fc-col-header-cell-cushion': {
|
||||
color: 'rgba(255, 255, 255, 0.8)',
|
||||
fontSize: { base: '0.75rem', md: '0.875rem' },
|
||||
padding: { base: '4px 2px', md: '8px' },
|
||||
},
|
||||
'.fc-daygrid-day': {
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
backgroundColor: 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: 'purple.50',
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.2)',
|
||||
},
|
||||
},
|
||||
'.fc-daygrid-day-number': {
|
||||
color: 'rgba(255, 255, 255, 0.9)',
|
||||
padding: { base: '2px', md: '4px' },
|
||||
fontSize: { base: '0.75rem', md: '0.875rem' },
|
||||
},
|
||||
'.fc-col-header-cell-cushion': {
|
||||
fontSize: { base: '0.75rem', md: '0.875rem' },
|
||||
padding: { base: '4px 2px', md: '8px' },
|
||||
'.fc-day-today': {
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.15) !important',
|
||||
},
|
||||
'.fc-day-other .fc-daygrid-day-number': {
|
||||
color: 'rgba(255, 255, 255, 0.4)',
|
||||
},
|
||||
'.fc-event': {
|
||||
cursor: 'pointer',
|
||||
@@ -855,12 +879,15 @@ const ConceptTimelineModal = ({
|
||||
transition: 'all 0.2s',
|
||||
'&:hover': {
|
||||
transform: 'scale(1.05)',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
|
||||
},
|
||||
},
|
||||
'.fc-daygrid-event-harness': {
|
||||
marginBottom: { base: '1px', md: '2px' },
|
||||
},
|
||||
'.fc-more-link': {
|
||||
color: 'rgba(255, 255, 255, 0.8)',
|
||||
},
|
||||
// H5 端隐藏事件文字,只显示色块
|
||||
'@media (max-width: 768px)': {
|
||||
'.fc-event-title': {
|
||||
@@ -898,24 +925,23 @@ const ConceptTimelineModal = ({
|
||||
<Center py={24}>
|
||||
<VStack
|
||||
spacing={6}
|
||||
bg="white"
|
||||
bg="rgba(15, 23, 42, 0.6)"
|
||||
p={12}
|
||||
borderRadius="2xl"
|
||||
boxShadow="xl"
|
||||
border="2px dashed"
|
||||
borderColor="purple.200"
|
||||
borderColor="whiteAlpha.200"
|
||||
>
|
||||
<Icon
|
||||
as={FaHistory}
|
||||
boxSize={24}
|
||||
color="purple.300"
|
||||
opacity={0.5}
|
||||
color="purple.400"
|
||||
opacity={0.6}
|
||||
/>
|
||||
<VStack spacing={2}>
|
||||
<Text fontSize="2xl" fontWeight="bold" color="gray.700">
|
||||
<Text fontSize="2xl" fontWeight="bold" color="white">
|
||||
暂无历史数据
|
||||
</Text>
|
||||
<Text fontSize="md" color="gray.500">
|
||||
<Text fontSize="md" color="whiteAlpha.600">
|
||||
该概念在最近100天内没有相关事件记录
|
||||
</Text>
|
||||
</VStack>
|
||||
@@ -941,45 +967,43 @@ const ConceptTimelineModal = ({
|
||||
size="4xl"
|
||||
scrollBehavior="inside"
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent borderRadius="2xl" overflow="hidden">
|
||||
<ModalOverlay bg="blackAlpha.800" backdropFilter="blur(8px)" />
|
||||
<ModalContent
|
||||
borderRadius="2xl"
|
||||
overflow="hidden"
|
||||
bg="rgba(15, 23, 42, 0.95)"
|
||||
border="1px solid"
|
||||
borderColor="whiteAlpha.100"
|
||||
>
|
||||
<ModalHeader
|
||||
bgGradient="linear(135deg, purple.600 0%, purple.500 50%, pink.500 100%)"
|
||||
bg="rgba(15, 23, 42, 0.98)"
|
||||
borderBottom="1px solid"
|
||||
borderColor="whiteAlpha.100"
|
||||
color="white"
|
||||
py={6}
|
||||
position="relative"
|
||||
_before={{
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
bgGradient: 'linear(to-r, transparent, whiteAlpha.200, transparent)',
|
||||
animation: `${shimmerAnimation} 3s infinite`,
|
||||
}}
|
||||
>
|
||||
<VStack align="start" spacing={3} position="relative" zIndex={1}>
|
||||
<HStack spacing={3}>
|
||||
<Icon
|
||||
as={CalendarIcon}
|
||||
boxSize={6}
|
||||
filter="drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
|
||||
color="cyan.400"
|
||||
/>
|
||||
<Text fontSize="xl" fontWeight="bold" textShadow="0 2px 4px rgba(0,0,0,0.2)">
|
||||
<Text fontSize="xl" fontWeight="bold" color="white">
|
||||
{formatDateDisplay(selectedDate)}
|
||||
</Text>
|
||||
</HStack>
|
||||
{selectedDateData.price && (
|
||||
<HStack spacing={4} fontSize="sm" flexWrap="wrap">
|
||||
<Badge
|
||||
colorScheme={getPriceInfo(selectedDateData.price).color}
|
||||
variant="solid"
|
||||
bg={getPriceInfo(selectedDateData.price).color === 'red' ? 'red.500' :
|
||||
getPriceInfo(selectedDateData.price).color === 'green' ? 'green.500' : 'whiteAlpha.300'}
|
||||
color="white"
|
||||
px={4}
|
||||
py={2}
|
||||
borderRadius="full"
|
||||
fontSize="md"
|
||||
boxShadow="md"
|
||||
>
|
||||
<HStack spacing={2}>
|
||||
{getPriceInfo(selectedDateData.price).icon && (
|
||||
@@ -996,14 +1020,13 @@ const ConceptTimelineModal = ({
|
||||
{selectedDateData.price.stock_count && (
|
||||
<HStack
|
||||
spacing={1}
|
||||
bg="whiteAlpha.300"
|
||||
bg="whiteAlpha.100"
|
||||
px={3}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
backdropFilter="blur(10px)"
|
||||
>
|
||||
<Text fontWeight="medium">📊 统计股票:</Text>
|
||||
<Text fontWeight="bold">{selectedDateData.price.stock_count} 只</Text>
|
||||
<Text fontWeight="medium" color="whiteAlpha.800">📊 统计股票:</Text>
|
||||
<Text fontWeight="bold" color="white">{selectedDateData.price.stock_count} 只</Text>
|
||||
</HStack>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -1012,7 +1035,7 @@ const ConceptTimelineModal = ({
|
||||
</ModalHeader>
|
||||
<ModalCloseButton color="white" />
|
||||
|
||||
<ModalBody py={6}>
|
||||
<ModalBody py={6} bg="transparent">
|
||||
{selectedDateData.events && selectedDateData.events.length > 0 ? (
|
||||
<VStack align="stretch" spacing={6}>
|
||||
{/* 统计卡片 */}
|
||||
@@ -1020,35 +1043,33 @@ const ConceptTimelineModal = ({
|
||||
<Box
|
||||
px={6}
|
||||
py={3}
|
||||
bg="blue.50"
|
||||
bg="whiteAlpha.100"
|
||||
borderRadius="lg"
|
||||
boxShadow="sm"
|
||||
border="1px solid"
|
||||
borderColor="blue.200"
|
||||
borderColor="blue.500"
|
||||
>
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FaNewspaper} color="blue.500" boxSize={5} />
|
||||
<Text fontWeight="bold" fontSize="lg" color="blue.700">
|
||||
<Icon as={FaNewspaper} color="blue.400" boxSize={5} />
|
||||
<Text fontWeight="bold" fontSize="lg" color="blue.300">
|
||||
{selectedDateData.events.filter(e => e.type === 'news').length}
|
||||
</Text>
|
||||
<Text fontSize="sm" color="gray.600">条新闻</Text>
|
||||
<Text fontSize="sm" color="whiteAlpha.700">条新闻</Text>
|
||||
</HStack>
|
||||
</Box>
|
||||
<Box
|
||||
px={6}
|
||||
py={3}
|
||||
bg="green.50"
|
||||
bg="whiteAlpha.100"
|
||||
borderRadius="lg"
|
||||
boxShadow="sm"
|
||||
border="1px solid"
|
||||
borderColor="green.200"
|
||||
borderColor="green.500"
|
||||
>
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FaFileAlt} color="green.500" boxSize={5} />
|
||||
<Text fontWeight="bold" fontSize="lg" color="green.700">
|
||||
<Icon as={FaFileAlt} color="green.400" boxSize={5} />
|
||||
<Text fontWeight="bold" fontSize="lg" color="green.300">
|
||||
{selectedDateData.events.filter(e => e.type === 'report').length}
|
||||
</Text>
|
||||
<Text fontSize="sm" color="gray.600">篇研报</Text>
|
||||
<Text fontSize="sm" color="whiteAlpha.700">篇研报</Text>
|
||||
</HStack>
|
||||
</Box>
|
||||
</HStack>
|
||||
@@ -1059,58 +1080,38 @@ const ConceptTimelineModal = ({
|
||||
<Box
|
||||
key={eventIdx}
|
||||
p={5}
|
||||
bgGradient={
|
||||
event.type === 'news'
|
||||
? 'linear(to-br, blue.50, blue.100)'
|
||||
: 'linear(to-br, green.50, green.100)'
|
||||
}
|
||||
bg="whiteAlpha.50"
|
||||
borderRadius="xl"
|
||||
borderLeft="5px solid"
|
||||
borderLeftColor={event.type === 'news' ? 'blue.500' : 'green.500'}
|
||||
boxShadow="sm"
|
||||
borderLeftColor={event.type === 'news' ? 'blue.400' : 'green.400'}
|
||||
border="1px solid"
|
||||
borderColor="whiteAlpha.100"
|
||||
_hover={{
|
||||
transform: 'translateX(6px) translateY(-2px)',
|
||||
boxShadow: 'lg',
|
||||
borderLeftColor: event.type === 'news' ? 'blue.600' : 'green.600',
|
||||
bg: 'whiteAlpha.100',
|
||||
borderLeftColor: event.type === 'news' ? 'blue.300' : 'green.300',
|
||||
}}
|
||||
transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
|
||||
cursor="pointer"
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
_before={{
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '3px',
|
||||
bgGradient: event.type === 'news'
|
||||
? 'linear(to-r, blue.400, blue.600, blue.400)'
|
||||
: 'linear(to-r, green.400, green.600, green.400)',
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.3s',
|
||||
}}
|
||||
_hover_before={{
|
||||
opacity: 1,
|
||||
}}
|
||||
>
|
||||
<VStack align="start" spacing={3}>
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
<Badge
|
||||
colorScheme={event.type === 'news' ? 'blue' : 'green'}
|
||||
variant="solid"
|
||||
bg={event.type === 'news' ? 'blue.500' : 'green.500'}
|
||||
color="white"
|
||||
fontSize="sm"
|
||||
px={3}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
boxShadow="sm"
|
||||
>
|
||||
{event.type === 'news' ? '📰 新闻' : '📊 研报'}
|
||||
</Badge>
|
||||
{event.publisher && (
|
||||
<Badge
|
||||
colorScheme="purple"
|
||||
variant="subtle"
|
||||
bg="purple.500"
|
||||
color="white"
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
py={1}
|
||||
@@ -1121,8 +1122,8 @@ const ConceptTimelineModal = ({
|
||||
)}
|
||||
{event.rating && (
|
||||
<Badge
|
||||
colorScheme="orange"
|
||||
variant="solid"
|
||||
bg="orange.500"
|
||||
color="white"
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
py={1}
|
||||
@@ -1133,8 +1134,8 @@ const ConceptTimelineModal = ({
|
||||
)}
|
||||
{event.security_name && (
|
||||
<Badge
|
||||
colorScheme="cyan"
|
||||
variant="subtle"
|
||||
bg="cyan.600"
|
||||
color="white"
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
py={1}
|
||||
@@ -1148,7 +1149,7 @@ const ConceptTimelineModal = ({
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontSize="lg"
|
||||
color="gray.800"
|
||||
color="white"
|
||||
lineHeight="1.4"
|
||||
>
|
||||
{event.title}
|
||||
@@ -1156,7 +1157,7 @@ const ConceptTimelineModal = ({
|
||||
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color="gray.600"
|
||||
color="whiteAlpha.700"
|
||||
noOfLines={3}
|
||||
lineHeight="1.6"
|
||||
>
|
||||
@@ -1166,8 +1167,8 @@ const ConceptTimelineModal = ({
|
||||
<HStack spacing={4} w="100%" justify="space-between" align="center" mt={1}>
|
||||
{event.time && (
|
||||
<HStack spacing={1}>
|
||||
<Icon as={FaClock} color="gray.500" boxSize={3} />
|
||||
<Text fontSize="xs" color="gray.500" fontWeight="medium">
|
||||
<Icon as={FaClock} color="whiteAlpha.500" boxSize={3} />
|
||||
<Text fontSize="xs" color="whiteAlpha.500" fontWeight="medium">
|
||||
{formatDateTime(event.time)}
|
||||
</Text>
|
||||
</HStack>
|
||||
@@ -1225,24 +1226,24 @@ const ConceptTimelineModal = ({
|
||||
<Center py={16}>
|
||||
<VStack
|
||||
spacing={5}
|
||||
bg="gray.50"
|
||||
bg="whiteAlpha.50"
|
||||
p={10}
|
||||
borderRadius="2xl"
|
||||
border="2px dashed"
|
||||
borderColor="gray.300"
|
||||
borderColor="whiteAlpha.200"
|
||||
>
|
||||
<Icon
|
||||
as={FaHistory}
|
||||
boxSize={20}
|
||||
color="gray.400"
|
||||
color="purple.400"
|
||||
opacity={0.6}
|
||||
/>
|
||||
<VStack spacing={2}>
|
||||
<Text fontSize="xl" fontWeight="bold" color="gray.600">
|
||||
<Text fontSize="xl" fontWeight="bold" color="white">
|
||||
当日无新闻或研报
|
||||
</Text>
|
||||
{selectedDateData.price && (
|
||||
<Text fontSize="md" color="gray.500">
|
||||
<Text fontSize="md" color="whiteAlpha.600">
|
||||
仅有涨跌幅数据
|
||||
</Text>
|
||||
)}
|
||||
@@ -1253,20 +1254,20 @@ const ConceptTimelineModal = ({
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter
|
||||
borderTop="2px solid"
|
||||
borderColor="gray.100"
|
||||
bg="gray.50"
|
||||
borderTop="1px solid"
|
||||
borderColor="whiteAlpha.100"
|
||||
bg="rgba(15, 23, 42, 0.98)"
|
||||
py={4}
|
||||
>
|
||||
<Button
|
||||
colorScheme="purple"
|
||||
bg="purple.500"
|
||||
color="white"
|
||||
size="lg"
|
||||
px={8}
|
||||
onClick={onDateDetailClose}
|
||||
boxShadow="md"
|
||||
_hover={{
|
||||
bg: 'purple.400',
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: 'lg',
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
>
|
||||
@@ -1285,30 +1286,30 @@ const ConceptTimelineModal = ({
|
||||
size="4xl"
|
||||
scrollBehavior="inside"
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader bg="green.500" color="white">
|
||||
<ModalOverlay bg="blackAlpha.800" backdropFilter="blur(8px)" />
|
||||
<ModalContent bg="rgba(15, 23, 42, 0.95)" border="1px solid" borderColor="whiteAlpha.100">
|
||||
<ModalHeader bg="rgba(15, 23, 42, 0.98)" borderBottom="1px solid" borderColor="whiteAlpha.100" color="white">
|
||||
<VStack align="start" spacing={1}>
|
||||
<Text fontSize="lg">{selectedReport?.title}</Text>
|
||||
<HStack spacing={3} fontSize="sm" opacity={0.9}>
|
||||
<Text fontSize="lg" color="white">{selectedReport?.title}</Text>
|
||||
<HStack spacing={3} fontSize="sm">
|
||||
{selectedReport?.publisher && (
|
||||
<Badge colorScheme="whiteAlpha" variant="solid">
|
||||
<Badge bg="green.500" color="white">
|
||||
{selectedReport.publisher}
|
||||
</Badge>
|
||||
)}
|
||||
{selectedReport?.author && (
|
||||
<Text>{selectedReport.author}</Text>
|
||||
<Text color="whiteAlpha.700">{selectedReport.author}</Text>
|
||||
)}
|
||||
{selectedReport?.time && (
|
||||
<Text>{formatDateTime(selectedReport.time)}</Text>
|
||||
<Text color="whiteAlpha.700">{formatDateTime(selectedReport.time)}</Text>
|
||||
)}
|
||||
{selectedReport?.rating && (
|
||||
<Badge colorScheme="orange" variant="solid">
|
||||
<Badge bg="orange.500" color="white">
|
||||
{selectedReport.rating}
|
||||
</Badge>
|
||||
)}
|
||||
{selectedReport?.security_name && (
|
||||
<Badge colorScheme="cyan" variant="solid">
|
||||
<Badge bg="cyan.600" color="white">
|
||||
{selectedReport.security_name}
|
||||
</Badge>
|
||||
)}
|
||||
@@ -1319,37 +1320,38 @@ const ConceptTimelineModal = ({
|
||||
|
||||
<ModalBody py={6}>
|
||||
<Box
|
||||
bg="gray.50"
|
||||
bg="whiteAlpha.50"
|
||||
p={6}
|
||||
borderRadius="md"
|
||||
border="1px solid"
|
||||
borderColor="gray.200"
|
||||
borderColor="whiteAlpha.100"
|
||||
>
|
||||
<Text
|
||||
whiteSpace="pre-wrap"
|
||||
fontSize="sm"
|
||||
lineHeight="tall"
|
||||
color="gray.700"
|
||||
color="whiteAlpha.800"
|
||||
>
|
||||
{selectedReport?.content || '暂无内容'}
|
||||
</Text>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter borderTop="1px solid" borderColor="gray.200">
|
||||
<ModalFooter borderTop="1px solid" borderColor="whiteAlpha.100" bg="rgba(15, 23, 42, 0.98)">
|
||||
<HStack spacing={3}>
|
||||
{selectedReport?.content_url && (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
bg="whiteAlpha.100"
|
||||
color="white"
|
||||
leftIcon={<ExternalLinkIcon />}
|
||||
onClick={() => window.open(selectedReport.content_url, '_blank')}
|
||||
_hover={{ bg: 'whiteAlpha.200' }}
|
||||
>
|
||||
查看原文
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" colorScheme="green" onClick={() => setIsReportModalOpen(false)}>
|
||||
<Button size="sm" bg="green.500" color="white" onClick={() => setIsReportModalOpen(false)} _hover={{ bg: 'green.400' }}>
|
||||
关闭
|
||||
</Button>
|
||||
</HStack>
|
||||
@@ -1366,22 +1368,22 @@ const ConceptTimelineModal = ({
|
||||
size="4xl"
|
||||
scrollBehavior="inside"
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader bg="blue.500" color="white">
|
||||
<ModalOverlay bg="blackAlpha.800" backdropFilter="blur(8px)" />
|
||||
<ModalContent bg="rgba(15, 23, 42, 0.95)" border="1px solid" borderColor="whiteAlpha.100">
|
||||
<ModalHeader bg="rgba(15, 23, 42, 0.98)" borderBottom="1px solid" borderColor="whiteAlpha.100" color="white">
|
||||
<VStack align="start" spacing={1}>
|
||||
<Text fontSize="lg">{selectedNews?.title}</Text>
|
||||
<HStack spacing={3} fontSize="sm" opacity={0.9}>
|
||||
<Text fontSize="lg" color="white">{selectedNews?.title}</Text>
|
||||
<HStack spacing={3} fontSize="sm">
|
||||
{selectedNews?.source && (
|
||||
<Badge
|
||||
colorScheme={selectedNews.source === 'zsxq' ? 'purple' : 'whiteAlpha'}
|
||||
variant="solid"
|
||||
bg={selectedNews.source === 'zsxq' ? 'purple.500' : 'blue.500'}
|
||||
color="white"
|
||||
>
|
||||
{selectedNews.source === 'zsxq' ? '知识星球' : selectedNews.source}
|
||||
</Badge>
|
||||
)}
|
||||
{selectedNews?.time && (
|
||||
<Text>{formatDateTime(selectedNews.time)}</Text>
|
||||
<Text color="whiteAlpha.700">{formatDateTime(selectedNews.time)}</Text>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
@@ -1390,38 +1392,39 @@ const ConceptTimelineModal = ({
|
||||
|
||||
<ModalBody py={6}>
|
||||
<Box
|
||||
bg="gray.50"
|
||||
bg="whiteAlpha.50"
|
||||
p={6}
|
||||
borderRadius="md"
|
||||
border="1px solid"
|
||||
borderColor="gray.200"
|
||||
borderColor="whiteAlpha.100"
|
||||
>
|
||||
<Text
|
||||
whiteSpace="pre-wrap"
|
||||
fontSize="sm"
|
||||
lineHeight="tall"
|
||||
color="gray.700"
|
||||
color="whiteAlpha.800"
|
||||
>
|
||||
{selectedNews?.content || '暂无内容'}
|
||||
</Text>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter borderTop="1px solid" borderColor="gray.200">
|
||||
<ModalFooter borderTop="1px solid" borderColor="whiteAlpha.100" bg="rgba(15, 23, 42, 0.98)">
|
||||
<HStack spacing={3}>
|
||||
{/* zsxq来源不显示查看原文按钮 */}
|
||||
{selectedNews?.url && selectedNews?.source !== 'zsxq' && (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
bg="whiteAlpha.100"
|
||||
color="white"
|
||||
leftIcon={<ExternalLinkIcon />}
|
||||
onClick={() => window.open(selectedNews.url, '_blank')}
|
||||
_hover={{ bg: 'whiteAlpha.200' }}
|
||||
>
|
||||
查看原文
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" colorScheme="blue" onClick={() => setIsNewsModalOpen(false)}>
|
||||
<Button size="sm" bg="blue.500" color="white" onClick={() => setIsNewsModalOpen(false)} _hover={{ bg: 'blue.400' }}>
|
||||
关闭
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
@@ -73,9 +73,10 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
const [useCustomRange, setUseCustomRange] = useState(false);
|
||||
const toast = useToast();
|
||||
|
||||
const bg = useColorModeValue('white', 'gray.800');
|
||||
const cardBg = useColorModeValue('gray.50', 'gray.700');
|
||||
const borderColor = useColorModeValue('gray.200', 'gray.600');
|
||||
// 深色主题颜色(固定使用深色模式)
|
||||
const bg = 'rgba(15, 23, 42, 0.8)';
|
||||
const cardBg = 'rgba(15, 23, 42, 0.6)';
|
||||
const borderColor = 'whiteAlpha.100';
|
||||
|
||||
// 获取统计数据
|
||||
const fetchStatsData = async (days = timeRange, startDate = null, endDate = null) => {
|
||||
@@ -250,21 +251,21 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
return 'gray.500';
|
||||
};
|
||||
|
||||
// 统计卡片组件 - 美化版
|
||||
// 统计卡片组件 - 深色主题版
|
||||
const StatsCard = ({ title, icon, color, data, renderItem, isLoading }) => (
|
||||
<Box p={4}>
|
||||
{isLoading ? (
|
||||
<VStack spacing={3} align="stretch">
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<HStack key={i} justify="space-between" p={3} bg="gray.50" borderRadius="lg">
|
||||
<HStack key={i} justify="space-between" p={3} bg="whiteAlpha.50" borderRadius="lg">
|
||||
<HStack spacing={2} flex={1}>
|
||||
<Skeleton height="20px" width="20px" borderRadius="full" />
|
||||
<Skeleton height="20px" width="20px" borderRadius="full" startColor="whiteAlpha.100" endColor="whiteAlpha.200" />
|
||||
<VStack align="start" spacing={1} flex={1}>
|
||||
<Skeleton height="14px" width="80%" />
|
||||
<Skeleton height="12px" width="60%" />
|
||||
<Skeleton height="14px" width="80%" startColor="whiteAlpha.100" endColor="whiteAlpha.200" />
|
||||
<Skeleton height="12px" width="60%" startColor="whiteAlpha.100" endColor="whiteAlpha.200" />
|
||||
</VStack>
|
||||
</HStack>
|
||||
<Skeleton height="20px" width="50px" borderRadius="md" />
|
||||
<Skeleton height="20px" width="50px" borderRadius="md" startColor="whiteAlpha.100" endColor="whiteAlpha.200" />
|
||||
</HStack>
|
||||
))}
|
||||
</VStack>
|
||||
@@ -275,15 +276,15 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
width: '4px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
background: '#f1f1f1',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
borderRadius: '10px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: '#c1c1c1',
|
||||
background: 'rgba(255,255,255,0.2)',
|
||||
borderRadius: '10px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
background: '#a8a8a8',
|
||||
background: 'rgba(255,255,255,0.3)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
@@ -309,14 +310,14 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
align="center"
|
||||
p={3}
|
||||
borderRadius="xl"
|
||||
bg={index < 3 ? 'red.50' : 'gray.50'}
|
||||
bg={index < 3 ? 'rgba(239, 68, 68, 0.15)' : 'whiteAlpha.50'}
|
||||
border="1px solid"
|
||||
borderColor={index < 3 ? 'red.100' : 'gray.200'}
|
||||
borderColor={index < 3 ? 'red.500' : 'whiteAlpha.100'}
|
||||
_hover={{
|
||||
transform: 'translateY(-1px)',
|
||||
shadow: 'md',
|
||||
cursor: 'pointer',
|
||||
bg: index < 3 ? 'red.100' : 'gray.100'
|
||||
bg: index < 3 ? 'rgba(239, 68, 68, 0.25)' : 'whiteAlpha.100'
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
onClick={() => onConceptClick?.(null, item.name)}
|
||||
@@ -324,8 +325,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
<HStack spacing={3} flex={1}>
|
||||
<Box position="relative">
|
||||
<Badge
|
||||
colorScheme={index === 0 ? 'yellow' : index === 1 ? 'orange' : index === 2 ? 'red' : 'gray'}
|
||||
variant="solid"
|
||||
bg={index === 0 ? 'yellow.500' : index === 1 ? 'orange.500' : index === 2 ? 'red.500' : 'whiteAlpha.200'}
|
||||
color="white"
|
||||
borderRadius="full"
|
||||
minW="24px"
|
||||
h="24px"
|
||||
@@ -344,16 +345,16 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
position="absolute"
|
||||
top="-8px"
|
||||
right="-8px"
|
||||
color="yellow.500"
|
||||
color="yellow.400"
|
||||
boxSize={3}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<VStack align="start" spacing={0} flex={1}>
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="gray.800">
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="white">
|
||||
{item.name}
|
||||
</Text>
|
||||
<HStack spacing={2} fontSize="xs" color="gray.600">
|
||||
<HStack spacing={2} fontSize="xs" color="whiteAlpha.600">
|
||||
<HStack spacing={1}>
|
||||
<Icon as={FaChartLine} boxSize={2.5} />
|
||||
<Text>{item.stock_count}股</Text>
|
||||
@@ -367,8 +368,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
</VStack>
|
||||
</HStack>
|
||||
<Badge
|
||||
colorScheme="red"
|
||||
variant="solid"
|
||||
bg="red.500"
|
||||
color="white"
|
||||
borderRadius="lg"
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
@@ -392,22 +393,22 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
align="center"
|
||||
p={3}
|
||||
borderRadius="xl"
|
||||
bg={index < 3 ? 'green.50' : 'gray.50'}
|
||||
bg={index < 3 ? 'rgba(34, 197, 94, 0.15)' : 'whiteAlpha.50'}
|
||||
border="1px solid"
|
||||
borderColor={index < 3 ? 'green.100' : 'gray.200'}
|
||||
borderColor={index < 3 ? 'green.500' : 'whiteAlpha.100'}
|
||||
_hover={{
|
||||
transform: 'translateY(-1px)',
|
||||
shadow: 'md',
|
||||
cursor: 'pointer',
|
||||
bg: index < 3 ? 'green.100' : 'gray.100'
|
||||
bg: index < 3 ? 'rgba(34, 197, 94, 0.25)' : 'whiteAlpha.100'
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
onClick={() => onConceptClick?.(null, item.name)}
|
||||
>
|
||||
<HStack spacing={3} flex={1}>
|
||||
<Badge
|
||||
colorScheme={index < 3 ? 'green' : 'gray'}
|
||||
variant="solid"
|
||||
bg={index < 3 ? 'green.500' : 'whiteAlpha.200'}
|
||||
color="white"
|
||||
borderRadius="full"
|
||||
minW="24px"
|
||||
h="24px"
|
||||
@@ -421,10 +422,10 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
{index + 1}
|
||||
</Badge>
|
||||
<VStack align="start" spacing={0} flex={1}>
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="gray.800">
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="white">
|
||||
{item.name}
|
||||
</Text>
|
||||
<HStack spacing={2} fontSize="xs" color="gray.600">
|
||||
<HStack spacing={2} fontSize="xs" color="whiteAlpha.600">
|
||||
<HStack spacing={1}>
|
||||
<Icon as={FaChartLine} boxSize={2.5} />
|
||||
<Text>{item.stock_count}股</Text>
|
||||
@@ -438,8 +439,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
</VStack>
|
||||
</HStack>
|
||||
<Badge
|
||||
colorScheme="green"
|
||||
variant="solid"
|
||||
bg="green.500"
|
||||
color="white"
|
||||
borderRadius="lg"
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
@@ -463,14 +464,14 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
align="center"
|
||||
p={3}
|
||||
borderRadius="xl"
|
||||
bg={index < 3 ? 'orange.50' : 'gray.50'}
|
||||
bg={index < 3 ? 'rgba(251, 146, 60, 0.15)' : 'whiteAlpha.50'}
|
||||
border="1px solid"
|
||||
borderColor={index < 3 ? 'orange.100' : 'gray.200'}
|
||||
borderColor={index < 3 ? 'orange.500' : 'whiteAlpha.100'}
|
||||
_hover={{
|
||||
transform: 'translateY(-1px)',
|
||||
shadow: 'md',
|
||||
cursor: 'pointer',
|
||||
bg: index < 3 ? 'orange.100' : 'gray.100'
|
||||
bg: index < 3 ? 'rgba(251, 146, 60, 0.25)' : 'whiteAlpha.100'
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
onClick={() => onConceptClick?.(null, item.name)}
|
||||
@@ -478,8 +479,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
<HStack spacing={3} flex={1}>
|
||||
<Box position="relative">
|
||||
<Badge
|
||||
colorScheme={index === 0 ? 'orange' : index < 3 ? 'yellow' : 'gray'}
|
||||
variant="solid"
|
||||
bg={index === 0 ? 'orange.500' : index < 3 ? 'yellow.500' : 'whiteAlpha.200'}
|
||||
color="white"
|
||||
borderRadius="full"
|
||||
minW="24px"
|
||||
h="24px"
|
||||
@@ -498,16 +499,16 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
position="absolute"
|
||||
top="-8px"
|
||||
right="-8px"
|
||||
color="orange.500"
|
||||
color="orange.400"
|
||||
boxSize={3}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<VStack align="start" spacing={0} flex={1}>
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="gray.800">
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="white">
|
||||
{item.name}
|
||||
</Text>
|
||||
<HStack spacing={2} fontSize="xs" color="gray.600">
|
||||
<HStack spacing={2} fontSize="xs" color="whiteAlpha.600">
|
||||
<HStack spacing={1}>
|
||||
<Icon as={FaNewspaper} boxSize={2.5} />
|
||||
<Text>{item.news_count}</Text>
|
||||
@@ -521,8 +522,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
</VStack>
|
||||
</HStack>
|
||||
<Badge
|
||||
colorScheme="orange"
|
||||
variant="solid"
|
||||
bg="orange.500"
|
||||
color="white"
|
||||
borderRadius="lg"
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
@@ -546,14 +547,14 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
align="center"
|
||||
p={3}
|
||||
borderRadius="xl"
|
||||
bg={index < 3 ? 'purple.50' : 'gray.50'}
|
||||
bg={index < 3 ? 'rgba(168, 85, 247, 0.15)' : 'whiteAlpha.50'}
|
||||
border="1px solid"
|
||||
borderColor={index < 3 ? 'purple.100' : 'gray.200'}
|
||||
borderColor={index < 3 ? 'purple.500' : 'whiteAlpha.100'}
|
||||
_hover={{
|
||||
transform: 'translateY(-1px)',
|
||||
shadow: 'md',
|
||||
cursor: 'pointer',
|
||||
bg: index < 3 ? 'purple.100' : 'gray.100'
|
||||
bg: index < 3 ? 'rgba(168, 85, 247, 0.25)' : 'whiteAlpha.100'
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
onClick={() => onConceptClick?.(null, item.name)}
|
||||
@@ -561,8 +562,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
<HStack spacing={3} flex={1}>
|
||||
<Box position="relative">
|
||||
<Badge
|
||||
colorScheme={index < 3 ? 'purple' : 'gray'}
|
||||
variant="solid"
|
||||
bg={index < 3 ? 'purple.500' : 'whiteAlpha.200'}
|
||||
color="white"
|
||||
borderRadius="full"
|
||||
minW="24px"
|
||||
h="24px"
|
||||
@@ -581,23 +582,23 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
position="absolute"
|
||||
top="-8px"
|
||||
right="-8px"
|
||||
color="purple.500"
|
||||
color="purple.400"
|
||||
boxSize={3}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<VStack align="start" spacing={0} flex={1}>
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="gray.800">
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="white">
|
||||
{item.name}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.600">
|
||||
<Text fontSize="xs" color="whiteAlpha.600">
|
||||
均幅 {formatChange(item.avg_change)}
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
<Badge
|
||||
colorScheme="purple"
|
||||
variant="solid"
|
||||
bg="purple.500"
|
||||
color="white"
|
||||
borderRadius="lg"
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
@@ -621,14 +622,14 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
align="center"
|
||||
p={3}
|
||||
borderRadius="xl"
|
||||
bg={index < 3 ? 'cyan.50' : 'gray.50'}
|
||||
bg={index < 3 ? 'rgba(34, 211, 238, 0.15)' : 'whiteAlpha.50'}
|
||||
border="1px solid"
|
||||
borderColor={index < 3 ? 'cyan.100' : 'gray.200'}
|
||||
borderColor={index < 3 ? 'cyan.500' : 'whiteAlpha.100'}
|
||||
_hover={{
|
||||
transform: 'translateY(-1px)',
|
||||
shadow: 'md',
|
||||
cursor: 'pointer',
|
||||
bg: index < 3 ? 'cyan.100' : 'gray.100'
|
||||
bg: index < 3 ? 'rgba(34, 211, 238, 0.25)' : 'whiteAlpha.100'
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
onClick={() => onConceptClick?.(null, item.name)}
|
||||
@@ -636,8 +637,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
<HStack spacing={3} flex={1}>
|
||||
<Box position="relative">
|
||||
<Badge
|
||||
colorScheme={index === 0 ? 'cyan' : index < 3 ? 'blue' : 'gray'}
|
||||
variant="solid"
|
||||
bg={index === 0 ? 'cyan.500' : index < 3 ? 'blue.500' : 'whiteAlpha.200'}
|
||||
color="white"
|
||||
borderRadius="full"
|
||||
minW="24px"
|
||||
h="24px"
|
||||
@@ -656,23 +657,23 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
position="absolute"
|
||||
top="-8px"
|
||||
right="-8px"
|
||||
color="cyan.500"
|
||||
color="cyan.400"
|
||||
boxSize={3}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<VStack align="start" spacing={0} flex={1}>
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="gray.800">
|
||||
<Text fontSize="sm" fontWeight="bold" noOfLines={1} color="white">
|
||||
{item.name}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.600">
|
||||
<Text fontSize="xs" color="whiteAlpha.600">
|
||||
累计 {formatChange(item.total_change)}
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
<Badge
|
||||
colorScheme="cyan"
|
||||
variant="solid"
|
||||
bg="cyan.500"
|
||||
color="white"
|
||||
borderRadius="lg"
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
@@ -689,14 +690,17 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* 顶部标题卡片 */}
|
||||
{/* 顶部标题卡片 - 深色玻璃态 */}
|
||||
<Box
|
||||
bg="linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||
bg="rgba(15, 23, 42, 0.8)"
|
||||
backdropFilter="blur(20px)"
|
||||
p={4}
|
||||
borderRadius="xl"
|
||||
mb={4}
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
border="1px solid"
|
||||
borderColor="whiteAlpha.100"
|
||||
>
|
||||
{/* 背景装饰 */}
|
||||
<Box
|
||||
@@ -706,8 +710,9 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
width="80px"
|
||||
height="80px"
|
||||
borderRadius="full"
|
||||
bg="whiteAlpha.200"
|
||||
filter="blur(10px)"
|
||||
bg="purple.500"
|
||||
opacity={0.2}
|
||||
filter="blur(20px)"
|
||||
/>
|
||||
<Box
|
||||
position="absolute"
|
||||
@@ -716,15 +721,16 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
width="60px"
|
||||
height="60px"
|
||||
borderRadius="full"
|
||||
bg="whiteAlpha.100"
|
||||
filter="blur(8px)"
|
||||
bg="cyan.500"
|
||||
opacity={0.15}
|
||||
filter="blur(15px)"
|
||||
/>
|
||||
|
||||
<VStack align="start" spacing={3} position="relative" w="full">
|
||||
<Flex justify="space-between" align="center" w="full">
|
||||
<HStack spacing={2}>
|
||||
<Box p={2} bg="whiteAlpha.200" borderRadius="lg">
|
||||
<Icon as={FaChartLine} color="white" boxSize={4} />
|
||||
<Box p={2} bg="whiteAlpha.100" borderRadius="lg" border="1px solid" borderColor="cyan.500">
|
||||
<Icon as={FaChartLine} color="cyan.400" boxSize={4} />
|
||||
</Box>
|
||||
<VStack align="start" spacing={0}>
|
||||
<Heading size="sm" color="white" fontWeight="bold">
|
||||
@@ -868,8 +874,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
</VStack>
|
||||
</Box>
|
||||
|
||||
{/* 主内容卡片 */}
|
||||
<Box bg={bg} borderRadius="xl" border="1px" borderColor={borderColor} shadow="sm" overflow="hidden">
|
||||
{/* 主内容卡片 - 深色玻璃态 */}
|
||||
<Box bg={bg} backdropFilter="blur(20px)" borderRadius="xl" border="1px" borderColor={borderColor} overflow="hidden">
|
||||
<Tabs
|
||||
index={activeTab}
|
||||
onChange={(index) => {
|
||||
@@ -882,7 +888,7 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
size="sm"
|
||||
>
|
||||
<TabList
|
||||
bg="gray.50"
|
||||
bg="rgba(15, 23, 42, 0.6)"
|
||||
borderBottom="1px"
|
||||
borderColor={borderColor}
|
||||
overflowX="auto"
|
||||
@@ -902,6 +908,7 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
px={3}
|
||||
py={3}
|
||||
whiteSpace="nowrap"
|
||||
color="whiteAlpha.700"
|
||||
_selected={{
|
||||
bg: `${tab.color}.500`,
|
||||
color: 'white',
|
||||
@@ -917,7 +924,7 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
|
||||
bg: `${tab.color}.500`,
|
||||
}
|
||||
}}
|
||||
_hover={{ bg: `${tab.color}.50` }}
|
||||
_hover={{ bg: 'whiteAlpha.100', color: 'white' }}
|
||||
transition="all 0.2s"
|
||||
>
|
||||
<HStack spacing={1}>
|
||||
|
||||
Reference in New Issue
Block a user