update pay ui

This commit is contained in:
2025-12-05 15:55:32 +08:00
parent b60c196f9e
commit c54318c3c9
3 changed files with 308 additions and 284 deletions

View File

@@ -26,15 +26,22 @@ import { FaTable } from 'react-icons/fa';
import marketService from '@services/marketService'; import marketService from '@services/marketService';
import { logger } from '@utils/logger'; import { logger } from '@utils/logger';
// 股票信息类型 // 股票信息类型 - 兼容新旧API格式
interface StockInfo { interface StockInfo {
stock_code: string; stock_code?: string;
stock_name: string; stock_name?: string;
code?: string; // 新API格式
name?: string; // 新API格式
reason?: string; reason?: string;
change_pct?: number; change_pct?: number;
[key: string]: unknown; [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 { export interface ConceptInfo {
concept_id?: string; concept_id?: string;
@@ -69,9 +76,12 @@ const ConceptStocksModal: React.FC<ConceptStocksModalProps> = ({
const [stockMarketData, setStockMarketData] = useState<Record<string, MarketData>>({}); const [stockMarketData, setStockMarketData] = useState<Record<string, MarketData>>({});
const [loadingStockData, setLoadingStockData] = useState(false); const [loadingStockData, setLoadingStockData] = useState(false);
// 色主题 // 色主题颜色
const cardBg = useColorModeValue('white', '#1a1a1a'); const cardBg = 'rgba(15, 23, 42, 0.95)';
const hoverBg = useColorModeValue('gray.50', '#2a2a2a'); const hoverBg = 'whiteAlpha.100';
const textColor = 'white';
const subTextColor = 'whiteAlpha.700';
const borderColor = 'whiteAlpha.100';
// 响应式配置 - 添加 fallback 避免首次渲染时返回 undefined 导致弹窗异常 // 响应式配置 - 添加 fallback 避免首次渲染时返回 undefined 导致弹窗异常
const isMobile = useBreakpointValue({ base: true, md: false }, { fallback: 'md' }); 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) { for (let i = 0; i < stocks.length; i += batchSize) {
const batch = stocks.slice(i, i + batchSize); const batch = stocks.slice(i, i + batchSize);
const promises = batch.map(async (stock) => { const promises = batch.map(async (stock) => {
if (!stock.stock_code) return null; const stockCode = getStockCode(stock);
const seccode = stock.stock_code.substring(0, 6); if (!stockCode) return null;
const seccode = stockCode.substring(0, 6);
try { try {
const response = await marketService.getTradeData(seccode, 1); const response = await marketService.getTradeData(seccode, 1);
if (response.success && response.data?.length > 0) { if (response.success && response.data?.length > 0) {
const latestData = response.data[response.data.length - 1]; const latestData = response.data[response.data.length - 1];
return { stock_code: stock.stock_code, ...latestData }; return { stock_code: stockCode, ...latestData };
} }
} catch (error) { } catch (error) {
logger.warn('ConceptStocksModal', '获取股票行情失败', { stockCode: seccode }); logger.warn('ConceptStocksModal', '获取股票行情失败', { stockCode: seccode });
@@ -168,16 +179,18 @@ const ConceptStocksModal: React.FC<ConceptStocksModalProps> = ({
<Table variant="simple" size="sm" minW={isMobile ? '600px' : undefined}> <Table variant="simple" size="sm" minW={isMobile ? '600px' : undefined}>
<Thead position="sticky" top={0} bg={cardBg} zIndex={1}> <Thead position="sticky" top={0} bg={cardBg} zIndex={1}>
<Tr> <Tr>
<Th whiteSpace="nowrap"></Th> <Th whiteSpace="nowrap" color={subTextColor} borderColor={borderColor}></Th>
<Th whiteSpace="nowrap"></Th> <Th whiteSpace="nowrap" color={subTextColor} borderColor={borderColor}></Th>
<Th isNumeric whiteSpace="nowrap"></Th> <Th isNumeric whiteSpace="nowrap" color={subTextColor} borderColor={borderColor}></Th>
<Th isNumeric whiteSpace="nowrap"></Th> <Th isNumeric whiteSpace="nowrap" color={subTextColor} borderColor={borderColor}></Th>
<Th whiteSpace="nowrap" minW="200px"></Th> <Th whiteSpace="nowrap" minW="200px" color={subTextColor} borderColor={borderColor}></Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{stocks.map((stock, idx) => { {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; const changePercent = marketData?.change_percent;
return ( return (
@@ -185,15 +198,15 @@ const ConceptStocksModal: React.FC<ConceptStocksModalProps> = ({
key={idx} key={idx}
_hover={{ bg: hoverBg }} _hover={{ bg: hoverBg }}
cursor="pointer" cursor="pointer"
onClick={() => handleStockClick(stock.stock_code)} onClick={() => handleStockClick(stockCode)}
> >
<Td color="blue.500" fontWeight="medium"> <Td color="cyan.400" fontWeight="medium" borderColor={borderColor}>
{stock.stock_name} {stockName}
</Td> </Td>
<Td>{stock.stock_code}</Td> <Td color={subTextColor} borderColor={borderColor}>{stockCode}</Td>
<Td isNumeric> <Td isNumeric color={textColor} borderColor={borderColor}>
{loadingStockData ? ( {loadingStockData ? (
<Spinner size="xs" /> <Spinner size="xs" color="purple.400" />
) : marketData?.close ? ( ) : marketData?.close ? (
`¥${marketData.close.toFixed(2)}` `¥${marketData.close.toFixed(2)}`
) : ( ) : (
@@ -203,23 +216,24 @@ const ConceptStocksModal: React.FC<ConceptStocksModalProps> = ({
<Td <Td
isNumeric isNumeric
fontWeight="bold" fontWeight="bold"
borderColor={borderColor}
color={ color={
changePercent && changePercent > 0 changePercent && changePercent > 0
? 'red.500' ? 'red.400'
: changePercent && changePercent < 0 : changePercent && changePercent < 0
? 'green.500' ? 'green.400'
: 'gray.500' : 'whiteAlpha.500'
} }
> >
{loadingStockData ? ( {loadingStockData ? (
<Spinner size="xs" /> <Spinner size="xs" color="purple.400" />
) : changePercent !== undefined ? ( ) : changePercent !== undefined ? (
`${changePercent > 0 ? '+' : ''}${changePercent.toFixed(2)}%` `${changePercent > 0 ? '+' : ''}${changePercent.toFixed(2)}%`
) : ( ) : (
'-' '-'
)} )}
</Td> </Td>
<Td fontSize="xs" color="gray.600" maxW="300px"> <Td fontSize="xs" color={subTextColor} maxW="300px" borderColor={borderColor}>
<Text noOfLines={2}>{stock.reason || '-'}</Text> <Text noOfLines={2}>{stock.reason || '-'}</Text>
</Td> </Td>
</Tr> </Tr>

View File

@@ -608,10 +608,20 @@ const ConceptTimelineModal = ({
scrollBehavior="inside" scrollBehavior="inside"
isCentered isCentered
> >
<ModalOverlay /> <ModalOverlay bg="blackAlpha.800" backdropFilter="blur(8px)" />
<ModalContent maxW="1400px" m={{ base: 0, md: 'auto' }} mx="auto"> <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 <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" color="white"
position="sticky" position="sticky"
top={0} top={0}
@@ -624,35 +634,34 @@ const ConceptTimelineModal = ({
<Icon <Icon
as={FaChartLine} as={FaChartLine}
boxSize={{ base: 4, md: 6 }} boxSize={{ base: 4, md: 6 }}
filter="drop-shadow(0 2px 4px rgba(0,0,0,0.2))" color="cyan.400"
/> />
<Text <Text
fontSize={{ base: 'md', md: 'xl' }} fontSize={{ base: 'md', md: 'xl' }}
fontWeight="bold" fontWeight="bold"
textShadow="0 2px 4px rgba(0,0,0,0.2)" color="white"
noOfLines={1} noOfLines={1}
maxW={{ base: '120px', md: 'none' }} maxW={{ base: '120px', md: 'none' }}
> >
{conceptName} - 历史时间轴 {conceptName} - 历史时间轴
</Text> </Text>
<Badge <Badge
colorScheme="yellow" bg="purple.500"
px={{ base: 2, md: 3 }}
py={1}
borderRadius="full"
fontSize={{ base: 'xs', md: 'sm' }}
boxShadow="md"
>
最近100天
</Badge>
<Badge
bg="whiteAlpha.300"
color="white" color="white"
px={{ base: 2, md: 3 }} px={{ base: 2, md: 3 }}
py={1} py={1}
borderRadius="full" 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" fontSize="xs"
backdropFilter="blur(10px)"
display={{ base: 'none', sm: 'flex' }} display={{ base: 'none', sm: 'flex' }}
> >
🔥 Max版功能 🔥 Max版功能
@@ -671,29 +680,29 @@ const ConceptTimelineModal = ({
<ModalBody <ModalBody
py={{ base: 2, md: 6 }} py={{ base: 2, md: 6 }}
px={{ base: 0, md: 6 }} px={{ base: 0, md: 6 }}
bg="gray.50" bg="transparent"
css={{ css={{
'&::-webkit-scrollbar': { '&::-webkit-scrollbar': {
width: '8px', width: '8px',
}, },
'&::-webkit-scrollbar-track': { '&::-webkit-scrollbar-track': {
background: '#f1f1f1', background: 'rgba(255,255,255,0.05)',
borderRadius: '10px', borderRadius: '10px',
}, },
'&::-webkit-scrollbar-thumb': { '&::-webkit-scrollbar-thumb': {
background: '#c1c1c1', background: 'rgba(255,255,255,0.2)',
borderRadius: '10px', borderRadius: '10px',
}, },
'&::-webkit-scrollbar-thumb:hover': { '&::-webkit-scrollbar-thumb:hover': {
background: '#a8a8a8', background: 'rgba(255,255,255,0.3)',
}, },
}} }}
> >
{loading ? ( {loading ? (
<Center py={20}> <Center py={20}>
<VStack spacing={4}> <VStack spacing={4}>
<Spinner size="xl" color="purple.500" thickness="4px" /> <Spinner size="xl" color="cyan.400" thickness="4px" />
<Text color="gray.600">正在加载时间轴数据...</Text> <Text color="whiteAlpha.700">正在加载时间轴数据...</Text>
</VStack> </VStack>
</Center> </Center>
) : timelineData.length > 0 ? ( ) : timelineData.length > 0 ? (
@@ -715,83 +724,79 @@ const ConceptTimelineModal = ({
spacing={{ base: 1, md: 2 }} spacing={{ base: 1, md: 2 }}
px={{ base: 2, md: 4 }} px={{ base: 2, md: 4 }}
py={{ base: 1, md: 2 }} py={{ base: 1, md: 2 }}
bg="purple.50" bg="whiteAlpha.100"
borderRadius="lg" borderRadius="lg"
border="1px solid" border="1px solid"
borderColor="purple.200" borderColor="purple.500"
boxShadow="sm"
flexShrink={0} flexShrink={0}
> >
<Box w={{ base: 2, md: 3 }} h={{ base: 2, md: 3 }} bg="#9F7AEA" borderRadius="full" /> <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>
<HStack <HStack
spacing={{ base: 1, md: 2 }} spacing={{ base: 1, md: 2 }}
px={{ base: 2, md: 4 }} px={{ base: 2, md: 4 }}
py={{ base: 1, md: 2 }} py={{ base: 1, md: 2 }}
bg="purple.50" bg="whiteAlpha.100"
borderRadius="lg" borderRadius="lg"
border="1px solid" border="1px solid"
borderColor="purple.300" borderColor="purple.600"
boxShadow="sm"
flexShrink={0} flexShrink={0}
> >
<Box w={{ base: 2, md: 3 }} h={{ base: 2, md: 3 }} bg="#805AD5" borderRadius="full" /> <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>
<HStack <HStack
spacing={{ base: 1, md: 2 }} spacing={{ base: 1, md: 2 }}
px={{ base: 2, md: 4 }} px={{ base: 2, md: 4 }}
py={{ base: 1, md: 2 }} py={{ base: 1, md: 2 }}
bg="red.50" bg="whiteAlpha.100"
borderRadius="lg" borderRadius="lg"
border="1px solid" border="1px solid"
borderColor="red.200" borderColor="red.400"
boxShadow="sm"
flexShrink={0} flexShrink={0}
> >
<Icon as={FaArrowUp} color="red.500" boxSize={{ base: 2, md: 3 }} /> <Icon as={FaArrowUp} color="red.400" boxSize={{ base: 2, md: 3 }} />
<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>
<HStack <HStack
spacing={{ base: 1, md: 2 }} spacing={{ base: 1, md: 2 }}
px={{ base: 2, md: 4 }} px={{ base: 2, md: 4 }}
py={{ base: 1, md: 2 }} py={{ base: 1, md: 2 }}
bg="green.50" bg="whiteAlpha.100"
borderRadius="lg" borderRadius="lg"
border="1px solid" border="1px solid"
borderColor="green.200" borderColor="green.400"
boxShadow="sm"
flexShrink={0} flexShrink={0}
> >
<Icon as={FaArrowDown} color="green.500" boxSize={{ base: 2, md: 3 }} /> <Icon as={FaArrowDown} color="green.400" boxSize={{ base: 2, md: 3 }} />
<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>
<HStack <HStack
spacing={{ base: 1, md: 2 }} spacing={{ base: 1, md: 2 }}
px={{ base: 2, md: 4 }} px={{ base: 2, md: 4 }}
py={{ base: 1, md: 2 }} py={{ base: 1, md: 2 }}
bg="orange.50" bg="whiteAlpha.100"
borderRadius="lg" borderRadius="lg"
border="1px solid" border="1px solid"
borderColor="orange.200" borderColor="orange.400"
boxShadow="sm"
flexShrink={0} flexShrink={0}
> >
<Text fontSize={{ base: 'xs', md: 'sm' }} fontWeight="bold">🔥</Text> <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> </HStack>
</Flex> </Flex>
{/* FullCalendar 日历组件 */} {/* FullCalendar 日历组件 */}
<Box <Box
height={{ base: '500px', md: '700px' }} height={{ base: '500px', md: '700px' }}
bg="white" bg="rgba(15, 23, 42, 0.6)"
borderRadius={{ base: 'none', md: 'xl' }} borderRadius={{ base: 'none', md: 'xl' }}
boxShadow={{ base: 'none', md: 'lg' }} border="1px solid"
borderColor="whiteAlpha.100"
p={{ base: 1, md: 4 }} p={{ base: 1, md: 4 }}
sx={{ sx={{
// FullCalendar 样式定制 // FullCalendar 深色主题样式定制
'.fc': { '.fc': {
height: '100%', height: '100%',
}, },
@@ -808,42 +813,61 @@ const ConceptTimelineModal = ({
'.fc-toolbar-title': { '.fc-toolbar-title': {
fontSize: { base: '1rem', md: '1.5rem' }, fontSize: { base: '1rem', md: '1.5rem' },
fontWeight: 'bold', fontWeight: 'bold',
color: 'purple.600', color: 'white',
}, },
'.fc-button': { '.fc-button': {
backgroundColor: '#9F7AEA', backgroundColor: 'rgba(139, 92, 246, 0.6)',
borderColor: '#9F7AEA', borderColor: 'rgba(139, 92, 246, 0.8)',
color: 'white', color: 'white',
padding: { base: '4px 8px', md: '6px 12px' }, padding: { base: '4px 8px', md: '6px 12px' },
fontSize: { base: '12px', md: '14px' }, fontSize: { base: '12px', md: '14px' },
'&:hover': { '&:hover': {
backgroundColor: '#805AD5', backgroundColor: 'rgba(139, 92, 246, 0.8)',
borderColor: '#805AD5', borderColor: 'rgba(139, 92, 246, 1)',
}, },
'&:active, &:focus': { '&:active, &:focus': {
backgroundColor: '#6B46C1', backgroundColor: 'rgba(139, 92, 246, 1)',
borderColor: '#6B46C1', borderColor: 'rgba(139, 92, 246, 1)',
boxShadow: 'none', boxShadow: 'none',
}, },
}, },
'.fc-button-active': { '.fc-button-active': {
backgroundColor: '#6B46C1', backgroundColor: 'rgba(139, 92, 246, 1)',
borderColor: '#6B46C1', 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': { '.fc-daygrid-day': {
cursor: 'pointer', cursor: 'pointer',
transition: 'all 0.2s', transition: 'all 0.2s',
backgroundColor: 'transparent',
'&:hover': { '&:hover': {
backgroundColor: 'purple.50', backgroundColor: 'rgba(139, 92, 246, 0.2)',
}, },
}, },
'.fc-daygrid-day-number': { '.fc-daygrid-day-number': {
color: 'rgba(255, 255, 255, 0.9)',
padding: { base: '2px', md: '4px' }, padding: { base: '2px', md: '4px' },
fontSize: { base: '0.75rem', md: '0.875rem' }, fontSize: { base: '0.75rem', md: '0.875rem' },
}, },
'.fc-col-header-cell-cushion': { '.fc-day-today': {
fontSize: { base: '0.75rem', md: '0.875rem' }, backgroundColor: 'rgba(139, 92, 246, 0.15) !important',
padding: { base: '4px 2px', md: '8px' }, },
'.fc-day-other .fc-daygrid-day-number': {
color: 'rgba(255, 255, 255, 0.4)',
}, },
'.fc-event': { '.fc-event': {
cursor: 'pointer', cursor: 'pointer',
@@ -855,12 +879,15 @@ const ConceptTimelineModal = ({
transition: 'all 0.2s', transition: 'all 0.2s',
'&:hover': { '&:hover': {
transform: 'scale(1.05)', 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': { '.fc-daygrid-event-harness': {
marginBottom: { base: '1px', md: '2px' }, marginBottom: { base: '1px', md: '2px' },
}, },
'.fc-more-link': {
color: 'rgba(255, 255, 255, 0.8)',
},
// H5 端隐藏事件文字,只显示色块 // H5 端隐藏事件文字,只显示色块
'@media (max-width: 768px)': { '@media (max-width: 768px)': {
'.fc-event-title': { '.fc-event-title': {
@@ -898,24 +925,23 @@ const ConceptTimelineModal = ({
<Center py={24}> <Center py={24}>
<VStack <VStack
spacing={6} spacing={6}
bg="white" bg="rgba(15, 23, 42, 0.6)"
p={12} p={12}
borderRadius="2xl" borderRadius="2xl"
boxShadow="xl"
border="2px dashed" border="2px dashed"
borderColor="purple.200" borderColor="whiteAlpha.200"
> >
<Icon <Icon
as={FaHistory} as={FaHistory}
boxSize={24} boxSize={24}
color="purple.300" color="purple.400"
opacity={0.5} opacity={0.6}
/> />
<VStack spacing={2}> <VStack spacing={2}>
<Text fontSize="2xl" fontWeight="bold" color="gray.700"> <Text fontSize="2xl" fontWeight="bold" color="white">
暂无历史数据 暂无历史数据
</Text> </Text>
<Text fontSize="md" color="gray.500"> <Text fontSize="md" color="whiteAlpha.600">
该概念在最近100天内没有相关事件记录 该概念在最近100天内没有相关事件记录
</Text> </Text>
</VStack> </VStack>
@@ -941,45 +967,43 @@ const ConceptTimelineModal = ({
size="4xl" size="4xl"
scrollBehavior="inside" scrollBehavior="inside"
> >
<ModalOverlay /> <ModalOverlay bg="blackAlpha.800" backdropFilter="blur(8px)" />
<ModalContent borderRadius="2xl" overflow="hidden"> <ModalContent
borderRadius="2xl"
overflow="hidden"
bg="rgba(15, 23, 42, 0.95)"
border="1px solid"
borderColor="whiteAlpha.100"
>
<ModalHeader <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" color="white"
py={6} py={6}
position="relative" 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}> <VStack align="start" spacing={3} position="relative" zIndex={1}>
<HStack spacing={3}> <HStack spacing={3}>
<Icon <Icon
as={CalendarIcon} as={CalendarIcon}
boxSize={6} 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)} {formatDateDisplay(selectedDate)}
</Text> </Text>
</HStack> </HStack>
{selectedDateData.price && ( {selectedDateData.price && (
<HStack spacing={4} fontSize="sm" flexWrap="wrap"> <HStack spacing={4} fontSize="sm" flexWrap="wrap">
<Badge <Badge
colorScheme={getPriceInfo(selectedDateData.price).color} bg={getPriceInfo(selectedDateData.price).color === 'red' ? 'red.500' :
variant="solid" getPriceInfo(selectedDateData.price).color === 'green' ? 'green.500' : 'whiteAlpha.300'}
color="white"
px={4} px={4}
py={2} py={2}
borderRadius="full" borderRadius="full"
fontSize="md" fontSize="md"
boxShadow="md"
> >
<HStack spacing={2}> <HStack spacing={2}>
{getPriceInfo(selectedDateData.price).icon && ( {getPriceInfo(selectedDateData.price).icon && (
@@ -996,14 +1020,13 @@ const ConceptTimelineModal = ({
{selectedDateData.price.stock_count && ( {selectedDateData.price.stock_count && (
<HStack <HStack
spacing={1} spacing={1}
bg="whiteAlpha.300" bg="whiteAlpha.100"
px={3} px={3}
py={1} py={1}
borderRadius="full" borderRadius="full"
backdropFilter="blur(10px)"
> >
<Text fontWeight="medium">📊 统计股票:</Text> <Text fontWeight="medium" color="whiteAlpha.800">📊 统计股票:</Text>
<Text fontWeight="bold">{selectedDateData.price.stock_count} </Text> <Text fontWeight="bold" color="white">{selectedDateData.price.stock_count} </Text>
</HStack> </HStack>
)} )}
</HStack> </HStack>
@@ -1012,7 +1035,7 @@ const ConceptTimelineModal = ({
</ModalHeader> </ModalHeader>
<ModalCloseButton color="white" /> <ModalCloseButton color="white" />
<ModalBody py={6}> <ModalBody py={6} bg="transparent">
{selectedDateData.events && selectedDateData.events.length > 0 ? ( {selectedDateData.events && selectedDateData.events.length > 0 ? (
<VStack align="stretch" spacing={6}> <VStack align="stretch" spacing={6}>
{/* 统计卡片 */} {/* 统计卡片 */}
@@ -1020,35 +1043,33 @@ const ConceptTimelineModal = ({
<Box <Box
px={6} px={6}
py={3} py={3}
bg="blue.50" bg="whiteAlpha.100"
borderRadius="lg" borderRadius="lg"
boxShadow="sm"
border="1px solid" border="1px solid"
borderColor="blue.200" borderColor="blue.500"
> >
<HStack spacing={2}> <HStack spacing={2}>
<Icon as={FaNewspaper} color="blue.500" boxSize={5} /> <Icon as={FaNewspaper} color="blue.400" boxSize={5} />
<Text fontWeight="bold" fontSize="lg" color="blue.700"> <Text fontWeight="bold" fontSize="lg" color="blue.300">
{selectedDateData.events.filter(e => e.type === 'news').length} {selectedDateData.events.filter(e => e.type === 'news').length}
</Text> </Text>
<Text fontSize="sm" color="gray.600">条新闻</Text> <Text fontSize="sm" color="whiteAlpha.700">条新闻</Text>
</HStack> </HStack>
</Box> </Box>
<Box <Box
px={6} px={6}
py={3} py={3}
bg="green.50" bg="whiteAlpha.100"
borderRadius="lg" borderRadius="lg"
boxShadow="sm"
border="1px solid" border="1px solid"
borderColor="green.200" borderColor="green.500"
> >
<HStack spacing={2}> <HStack spacing={2}>
<Icon as={FaFileAlt} color="green.500" boxSize={5} /> <Icon as={FaFileAlt} color="green.400" boxSize={5} />
<Text fontWeight="bold" fontSize="lg" color="green.700"> <Text fontWeight="bold" fontSize="lg" color="green.300">
{selectedDateData.events.filter(e => e.type === 'report').length} {selectedDateData.events.filter(e => e.type === 'report').length}
</Text> </Text>
<Text fontSize="sm" color="gray.600">篇研报</Text> <Text fontSize="sm" color="whiteAlpha.700">篇研报</Text>
</HStack> </HStack>
</Box> </Box>
</HStack> </HStack>
@@ -1059,58 +1080,38 @@ const ConceptTimelineModal = ({
<Box <Box
key={eventIdx} key={eventIdx}
p={5} p={5}
bgGradient={ bg="whiteAlpha.50"
event.type === 'news'
? 'linear(to-br, blue.50, blue.100)'
: 'linear(to-br, green.50, green.100)'
}
borderRadius="xl" borderRadius="xl"
borderLeft="5px solid" borderLeft="5px solid"
borderLeftColor={event.type === 'news' ? 'blue.500' : 'green.500'} borderLeftColor={event.type === 'news' ? 'blue.400' : 'green.400'}
boxShadow="sm" border="1px solid"
borderColor="whiteAlpha.100"
_hover={{ _hover={{
transform: 'translateX(6px) translateY(-2px)', transform: 'translateX(6px) translateY(-2px)',
boxShadow: 'lg', bg: 'whiteAlpha.100',
borderLeftColor: event.type === 'news' ? 'blue.600' : 'green.600', borderLeftColor: event.type === 'news' ? 'blue.300' : 'green.300',
}} }}
transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)" transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
cursor="pointer" cursor="pointer"
position="relative" position="relative"
overflow="hidden" 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}> <VStack align="start" spacing={3}>
<HStack spacing={2} flexWrap="wrap"> <HStack spacing={2} flexWrap="wrap">
<Badge <Badge
colorScheme={event.type === 'news' ? 'blue' : 'green'} bg={event.type === 'news' ? 'blue.500' : 'green.500'}
variant="solid" color="white"
fontSize="sm" fontSize="sm"
px={3} px={3}
py={1} py={1}
borderRadius="full" borderRadius="full"
boxShadow="sm"
> >
{event.type === 'news' ? '📰 新闻' : '📊 研报'} {event.type === 'news' ? '📰 新闻' : '📊 研报'}
</Badge> </Badge>
{event.publisher && ( {event.publisher && (
<Badge <Badge
colorScheme="purple" bg="purple.500"
variant="subtle" color="white"
fontSize="xs" fontSize="xs"
px={2} px={2}
py={1} py={1}
@@ -1121,8 +1122,8 @@ const ConceptTimelineModal = ({
)} )}
{event.rating && ( {event.rating && (
<Badge <Badge
colorScheme="orange" bg="orange.500"
variant="solid" color="white"
fontSize="xs" fontSize="xs"
px={2} px={2}
py={1} py={1}
@@ -1133,8 +1134,8 @@ const ConceptTimelineModal = ({
)} )}
{event.security_name && ( {event.security_name && (
<Badge <Badge
colorScheme="cyan" bg="cyan.600"
variant="subtle" color="white"
fontSize="xs" fontSize="xs"
px={2} px={2}
py={1} py={1}
@@ -1148,7 +1149,7 @@ const ConceptTimelineModal = ({
<Text <Text
fontWeight="bold" fontWeight="bold"
fontSize="lg" fontSize="lg"
color="gray.800" color="white"
lineHeight="1.4" lineHeight="1.4"
> >
{event.title} {event.title}
@@ -1156,7 +1157,7 @@ const ConceptTimelineModal = ({
<Text <Text
fontSize="sm" fontSize="sm"
color="gray.600" color="whiteAlpha.700"
noOfLines={3} noOfLines={3}
lineHeight="1.6" lineHeight="1.6"
> >
@@ -1166,8 +1167,8 @@ const ConceptTimelineModal = ({
<HStack spacing={4} w="100%" justify="space-between" align="center" mt={1}> <HStack spacing={4} w="100%" justify="space-between" align="center" mt={1}>
{event.time && ( {event.time && (
<HStack spacing={1}> <HStack spacing={1}>
<Icon as={FaClock} color="gray.500" boxSize={3} /> <Icon as={FaClock} color="whiteAlpha.500" boxSize={3} />
<Text fontSize="xs" color="gray.500" fontWeight="medium"> <Text fontSize="xs" color="whiteAlpha.500" fontWeight="medium">
{formatDateTime(event.time)} {formatDateTime(event.time)}
</Text> </Text>
</HStack> </HStack>
@@ -1225,24 +1226,24 @@ const ConceptTimelineModal = ({
<Center py={16}> <Center py={16}>
<VStack <VStack
spacing={5} spacing={5}
bg="gray.50" bg="whiteAlpha.50"
p={10} p={10}
borderRadius="2xl" borderRadius="2xl"
border="2px dashed" border="2px dashed"
borderColor="gray.300" borderColor="whiteAlpha.200"
> >
<Icon <Icon
as={FaHistory} as={FaHistory}
boxSize={20} boxSize={20}
color="gray.400" color="purple.400"
opacity={0.6} opacity={0.6}
/> />
<VStack spacing={2}> <VStack spacing={2}>
<Text fontSize="xl" fontWeight="bold" color="gray.600"> <Text fontSize="xl" fontWeight="bold" color="white">
当日无新闻或研报 当日无新闻或研报
</Text> </Text>
{selectedDateData.price && ( {selectedDateData.price && (
<Text fontSize="md" color="gray.500"> <Text fontSize="md" color="whiteAlpha.600">
仅有涨跌幅数据 仅有涨跌幅数据
</Text> </Text>
)} )}
@@ -1253,20 +1254,20 @@ const ConceptTimelineModal = ({
</ModalBody> </ModalBody>
<ModalFooter <ModalFooter
borderTop="2px solid" borderTop="1px solid"
borderColor="gray.100" borderColor="whiteAlpha.100"
bg="gray.50" bg="rgba(15, 23, 42, 0.98)"
py={4} py={4}
> >
<Button <Button
colorScheme="purple" bg="purple.500"
color="white"
size="lg" size="lg"
px={8} px={8}
onClick={onDateDetailClose} onClick={onDateDetailClose}
boxShadow="md"
_hover={{ _hover={{
bg: 'purple.400',
transform: 'translateY(-2px)', transform: 'translateY(-2px)',
boxShadow: 'lg',
}} }}
transition="all 0.2s" transition="all 0.2s"
> >
@@ -1285,30 +1286,30 @@ const ConceptTimelineModal = ({
size="4xl" size="4xl"
scrollBehavior="inside" scrollBehavior="inside"
> >
<ModalOverlay /> <ModalOverlay bg="blackAlpha.800" backdropFilter="blur(8px)" />
<ModalContent> <ModalContent bg="rgba(15, 23, 42, 0.95)" border="1px solid" borderColor="whiteAlpha.100">
<ModalHeader bg="green.500" color="white"> <ModalHeader bg="rgba(15, 23, 42, 0.98)" borderBottom="1px solid" borderColor="whiteAlpha.100" color="white">
<VStack align="start" spacing={1}> <VStack align="start" spacing={1}>
<Text fontSize="lg">{selectedReport?.title}</Text> <Text fontSize="lg" color="white">{selectedReport?.title}</Text>
<HStack spacing={3} fontSize="sm" opacity={0.9}> <HStack spacing={3} fontSize="sm">
{selectedReport?.publisher && ( {selectedReport?.publisher && (
<Badge colorScheme="whiteAlpha" variant="solid"> <Badge bg="green.500" color="white">
{selectedReport.publisher} {selectedReport.publisher}
</Badge> </Badge>
)} )}
{selectedReport?.author && ( {selectedReport?.author && (
<Text>{selectedReport.author}</Text> <Text color="whiteAlpha.700">{selectedReport.author}</Text>
)} )}
{selectedReport?.time && ( {selectedReport?.time && (
<Text>{formatDateTime(selectedReport.time)}</Text> <Text color="whiteAlpha.700">{formatDateTime(selectedReport.time)}</Text>
)} )}
{selectedReport?.rating && ( {selectedReport?.rating && (
<Badge colorScheme="orange" variant="solid"> <Badge bg="orange.500" color="white">
{selectedReport.rating} {selectedReport.rating}
</Badge> </Badge>
)} )}
{selectedReport?.security_name && ( {selectedReport?.security_name && (
<Badge colorScheme="cyan" variant="solid"> <Badge bg="cyan.600" color="white">
{selectedReport.security_name} {selectedReport.security_name}
</Badge> </Badge>
)} )}
@@ -1319,37 +1320,38 @@ const ConceptTimelineModal = ({
<ModalBody py={6}> <ModalBody py={6}>
<Box <Box
bg="gray.50" bg="whiteAlpha.50"
p={6} p={6}
borderRadius="md" borderRadius="md"
border="1px solid" border="1px solid"
borderColor="gray.200" borderColor="whiteAlpha.100"
> >
<Text <Text
whiteSpace="pre-wrap" whiteSpace="pre-wrap"
fontSize="sm" fontSize="sm"
lineHeight="tall" lineHeight="tall"
color="gray.700" color="whiteAlpha.800"
> >
{selectedReport?.content || '暂无内容'} {selectedReport?.content || '暂无内容'}
</Text> </Text>
</Box> </Box>
</ModalBody> </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}> <HStack spacing={3}>
{selectedReport?.content_url && ( {selectedReport?.content_url && (
<Button <Button
size="sm" size="sm"
colorScheme="blue" bg="whiteAlpha.100"
variant="outline" color="white"
leftIcon={<ExternalLinkIcon />} leftIcon={<ExternalLinkIcon />}
onClick={() => window.open(selectedReport.content_url, '_blank')} onClick={() => window.open(selectedReport.content_url, '_blank')}
_hover={{ bg: 'whiteAlpha.200' }}
> >
查看原文 查看原文
</Button> </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> </Button>
</HStack> </HStack>
@@ -1366,22 +1368,22 @@ const ConceptTimelineModal = ({
size="4xl" size="4xl"
scrollBehavior="inside" scrollBehavior="inside"
> >
<ModalOverlay /> <ModalOverlay bg="blackAlpha.800" backdropFilter="blur(8px)" />
<ModalContent> <ModalContent bg="rgba(15, 23, 42, 0.95)" border="1px solid" borderColor="whiteAlpha.100">
<ModalHeader bg="blue.500" color="white"> <ModalHeader bg="rgba(15, 23, 42, 0.98)" borderBottom="1px solid" borderColor="whiteAlpha.100" color="white">
<VStack align="start" spacing={1}> <VStack align="start" spacing={1}>
<Text fontSize="lg">{selectedNews?.title}</Text> <Text fontSize="lg" color="white">{selectedNews?.title}</Text>
<HStack spacing={3} fontSize="sm" opacity={0.9}> <HStack spacing={3} fontSize="sm">
{selectedNews?.source && ( {selectedNews?.source && (
<Badge <Badge
colorScheme={selectedNews.source === 'zsxq' ? 'purple' : 'whiteAlpha'} bg={selectedNews.source === 'zsxq' ? 'purple.500' : 'blue.500'}
variant="solid" color="white"
> >
{selectedNews.source === 'zsxq' ? '知识星球' : selectedNews.source} {selectedNews.source === 'zsxq' ? '知识星球' : selectedNews.source}
</Badge> </Badge>
)} )}
{selectedNews?.time && ( {selectedNews?.time && (
<Text>{formatDateTime(selectedNews.time)}</Text> <Text color="whiteAlpha.700">{formatDateTime(selectedNews.time)}</Text>
)} )}
</HStack> </HStack>
</VStack> </VStack>
@@ -1390,38 +1392,39 @@ const ConceptTimelineModal = ({
<ModalBody py={6}> <ModalBody py={6}>
<Box <Box
bg="gray.50" bg="whiteAlpha.50"
p={6} p={6}
borderRadius="md" borderRadius="md"
border="1px solid" border="1px solid"
borderColor="gray.200" borderColor="whiteAlpha.100"
> >
<Text <Text
whiteSpace="pre-wrap" whiteSpace="pre-wrap"
fontSize="sm" fontSize="sm"
lineHeight="tall" lineHeight="tall"
color="gray.700" color="whiteAlpha.800"
> >
{selectedNews?.content || '暂无内容'} {selectedNews?.content || '暂无内容'}
</Text> </Text>
</Box> </Box>
</ModalBody> </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}> <HStack spacing={3}>
{/* zsxq来源不显示查看原文按钮 */} {/* zsxq来源不显示查看原文按钮 */}
{selectedNews?.url && selectedNews?.source !== 'zsxq' && ( {selectedNews?.url && selectedNews?.source !== 'zsxq' && (
<Button <Button
size="sm" size="sm"
colorScheme="blue" bg="whiteAlpha.100"
variant="outline" color="white"
leftIcon={<ExternalLinkIcon />} leftIcon={<ExternalLinkIcon />}
onClick={() => window.open(selectedNews.url, '_blank')} onClick={() => window.open(selectedNews.url, '_blank')}
_hover={{ bg: 'whiteAlpha.200' }}
> >
查看原文 查看原文
</Button> </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> </Button>
</HStack> </HStack>

View File

@@ -73,9 +73,10 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
const [useCustomRange, setUseCustomRange] = useState(false); const [useCustomRange, setUseCustomRange] = useState(false);
const toast = useToast(); const toast = useToast();
const bg = useColorModeValue('white', 'gray.800'); // 深色主题颜色(固定使用深色模式)
const cardBg = useColorModeValue('gray.50', 'gray.700'); const bg = 'rgba(15, 23, 42, 0.8)';
const borderColor = useColorModeValue('gray.200', 'gray.600'); const cardBg = 'rgba(15, 23, 42, 0.6)';
const borderColor = 'whiteAlpha.100';
// 获取统计数据 // 获取统计数据
const fetchStatsData = async (days = timeRange, startDate = null, endDate = null) => { const fetchStatsData = async (days = timeRange, startDate = null, endDate = null) => {
@@ -250,21 +251,21 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
return 'gray.500'; return 'gray.500';
}; };
// 统计卡片组件 - 美化 // 统计卡片组件 - 深色主题
const StatsCard = ({ title, icon, color, data, renderItem, isLoading }) => ( const StatsCard = ({ title, icon, color, data, renderItem, isLoading }) => (
<Box p={4}> <Box p={4}>
{isLoading ? ( {isLoading ? (
<VStack spacing={3} align="stretch"> <VStack spacing={3} align="stretch">
{[1, 2, 3, 4, 5].map((i) => ( {[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}> <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}> <VStack align="start" spacing={1} flex={1}>
<Skeleton height="14px" width="80%" /> <Skeleton height="14px" width="80%" startColor="whiteAlpha.100" endColor="whiteAlpha.200" />
<Skeleton height="12px" width="60%" /> <Skeleton height="12px" width="60%" startColor="whiteAlpha.100" endColor="whiteAlpha.200" />
</VStack> </VStack>
</HStack> </HStack>
<Skeleton height="20px" width="50px" borderRadius="md" /> <Skeleton height="20px" width="50px" borderRadius="md" startColor="whiteAlpha.100" endColor="whiteAlpha.200" />
</HStack> </HStack>
))} ))}
</VStack> </VStack>
@@ -275,15 +276,15 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
width: '4px', width: '4px',
}, },
'&::-webkit-scrollbar-track': { '&::-webkit-scrollbar-track': {
background: '#f1f1f1', background: 'rgba(255,255,255,0.05)',
borderRadius: '10px', borderRadius: '10px',
}, },
'&::-webkit-scrollbar-thumb': { '&::-webkit-scrollbar-thumb': {
background: '#c1c1c1', background: 'rgba(255,255,255,0.2)',
borderRadius: '10px', borderRadius: '10px',
}, },
'&::-webkit-scrollbar-thumb:hover': { '&::-webkit-scrollbar-thumb:hover': {
background: '#a8a8a8', background: 'rgba(255,255,255,0.3)',
}, },
}} }}
> >
@@ -309,14 +310,14 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
align="center" align="center"
p={3} p={3}
borderRadius="xl" borderRadius="xl"
bg={index < 3 ? 'red.50' : 'gray.50'} bg={index < 3 ? 'rgba(239, 68, 68, 0.15)' : 'whiteAlpha.50'}
border="1px solid" border="1px solid"
borderColor={index < 3 ? 'red.100' : 'gray.200'} borderColor={index < 3 ? 'red.500' : 'whiteAlpha.100'}
_hover={{ _hover={{
transform: 'translateY(-1px)', transform: 'translateY(-1px)',
shadow: 'md', shadow: 'md',
cursor: 'pointer', cursor: 'pointer',
bg: index < 3 ? 'red.100' : 'gray.100' bg: index < 3 ? 'rgba(239, 68, 68, 0.25)' : 'whiteAlpha.100'
}} }}
transition="all 0.2s" transition="all 0.2s"
onClick={() => onConceptClick?.(null, item.name)} onClick={() => onConceptClick?.(null, item.name)}
@@ -324,8 +325,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
<HStack spacing={3} flex={1}> <HStack spacing={3} flex={1}>
<Box position="relative"> <Box position="relative">
<Badge <Badge
colorScheme={index === 0 ? 'yellow' : index === 1 ? 'orange' : index === 2 ? 'red' : 'gray'} bg={index === 0 ? 'yellow.500' : index === 1 ? 'orange.500' : index === 2 ? 'red.500' : 'whiteAlpha.200'}
variant="solid" color="white"
borderRadius="full" borderRadius="full"
minW="24px" minW="24px"
h="24px" h="24px"
@@ -344,16 +345,16 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
position="absolute" position="absolute"
top="-8px" top="-8px"
right="-8px" right="-8px"
color="yellow.500" color="yellow.400"
boxSize={3} boxSize={3}
/> />
)} )}
</Box> </Box>
<VStack align="start" spacing={0} flex={1}> <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} {item.name}
</Text> </Text>
<HStack spacing={2} fontSize="xs" color="gray.600"> <HStack spacing={2} fontSize="xs" color="whiteAlpha.600">
<HStack spacing={1}> <HStack spacing={1}>
<Icon as={FaChartLine} boxSize={2.5} /> <Icon as={FaChartLine} boxSize={2.5} />
<Text>{item.stock_count}</Text> <Text>{item.stock_count}</Text>
@@ -367,8 +368,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
</VStack> </VStack>
</HStack> </HStack>
<Badge <Badge
colorScheme="red" bg="red.500"
variant="solid" color="white"
borderRadius="lg" borderRadius="lg"
fontSize="xs" fontSize="xs"
fontWeight="bold" fontWeight="bold"
@@ -392,22 +393,22 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
align="center" align="center"
p={3} p={3}
borderRadius="xl" borderRadius="xl"
bg={index < 3 ? 'green.50' : 'gray.50'} bg={index < 3 ? 'rgba(34, 197, 94, 0.15)' : 'whiteAlpha.50'}
border="1px solid" border="1px solid"
borderColor={index < 3 ? 'green.100' : 'gray.200'} borderColor={index < 3 ? 'green.500' : 'whiteAlpha.100'}
_hover={{ _hover={{
transform: 'translateY(-1px)', transform: 'translateY(-1px)',
shadow: 'md', shadow: 'md',
cursor: 'pointer', cursor: 'pointer',
bg: index < 3 ? 'green.100' : 'gray.100' bg: index < 3 ? 'rgba(34, 197, 94, 0.25)' : 'whiteAlpha.100'
}} }}
transition="all 0.2s" transition="all 0.2s"
onClick={() => onConceptClick?.(null, item.name)} onClick={() => onConceptClick?.(null, item.name)}
> >
<HStack spacing={3} flex={1}> <HStack spacing={3} flex={1}>
<Badge <Badge
colorScheme={index < 3 ? 'green' : 'gray'} bg={index < 3 ? 'green.500' : 'whiteAlpha.200'}
variant="solid" color="white"
borderRadius="full" borderRadius="full"
minW="24px" minW="24px"
h="24px" h="24px"
@@ -421,10 +422,10 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
{index + 1} {index + 1}
</Badge> </Badge>
<VStack align="start" spacing={0} flex={1}> <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} {item.name}
</Text> </Text>
<HStack spacing={2} fontSize="xs" color="gray.600"> <HStack spacing={2} fontSize="xs" color="whiteAlpha.600">
<HStack spacing={1}> <HStack spacing={1}>
<Icon as={FaChartLine} boxSize={2.5} /> <Icon as={FaChartLine} boxSize={2.5} />
<Text>{item.stock_count}</Text> <Text>{item.stock_count}</Text>
@@ -438,8 +439,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
</VStack> </VStack>
</HStack> </HStack>
<Badge <Badge
colorScheme="green" bg="green.500"
variant="solid" color="white"
borderRadius="lg" borderRadius="lg"
fontSize="xs" fontSize="xs"
fontWeight="bold" fontWeight="bold"
@@ -463,14 +464,14 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
align="center" align="center"
p={3} p={3}
borderRadius="xl" borderRadius="xl"
bg={index < 3 ? 'orange.50' : 'gray.50'} bg={index < 3 ? 'rgba(251, 146, 60, 0.15)' : 'whiteAlpha.50'}
border="1px solid" border="1px solid"
borderColor={index < 3 ? 'orange.100' : 'gray.200'} borderColor={index < 3 ? 'orange.500' : 'whiteAlpha.100'}
_hover={{ _hover={{
transform: 'translateY(-1px)', transform: 'translateY(-1px)',
shadow: 'md', shadow: 'md',
cursor: 'pointer', cursor: 'pointer',
bg: index < 3 ? 'orange.100' : 'gray.100' bg: index < 3 ? 'rgba(251, 146, 60, 0.25)' : 'whiteAlpha.100'
}} }}
transition="all 0.2s" transition="all 0.2s"
onClick={() => onConceptClick?.(null, item.name)} onClick={() => onConceptClick?.(null, item.name)}
@@ -478,8 +479,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
<HStack spacing={3} flex={1}> <HStack spacing={3} flex={1}>
<Box position="relative"> <Box position="relative">
<Badge <Badge
colorScheme={index === 0 ? 'orange' : index < 3 ? 'yellow' : 'gray'} bg={index === 0 ? 'orange.500' : index < 3 ? 'yellow.500' : 'whiteAlpha.200'}
variant="solid" color="white"
borderRadius="full" borderRadius="full"
minW="24px" minW="24px"
h="24px" h="24px"
@@ -498,16 +499,16 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
position="absolute" position="absolute"
top="-8px" top="-8px"
right="-8px" right="-8px"
color="orange.500" color="orange.400"
boxSize={3} boxSize={3}
/> />
)} )}
</Box> </Box>
<VStack align="start" spacing={0} flex={1}> <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} {item.name}
</Text> </Text>
<HStack spacing={2} fontSize="xs" color="gray.600"> <HStack spacing={2} fontSize="xs" color="whiteAlpha.600">
<HStack spacing={1}> <HStack spacing={1}>
<Icon as={FaNewspaper} boxSize={2.5} /> <Icon as={FaNewspaper} boxSize={2.5} />
<Text>{item.news_count}</Text> <Text>{item.news_count}</Text>
@@ -521,8 +522,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
</VStack> </VStack>
</HStack> </HStack>
<Badge <Badge
colorScheme="orange" bg="orange.500"
variant="solid" color="white"
borderRadius="lg" borderRadius="lg"
fontSize="xs" fontSize="xs"
fontWeight="bold" fontWeight="bold"
@@ -546,14 +547,14 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
align="center" align="center"
p={3} p={3}
borderRadius="xl" borderRadius="xl"
bg={index < 3 ? 'purple.50' : 'gray.50'} bg={index < 3 ? 'rgba(168, 85, 247, 0.15)' : 'whiteAlpha.50'}
border="1px solid" border="1px solid"
borderColor={index < 3 ? 'purple.100' : 'gray.200'} borderColor={index < 3 ? 'purple.500' : 'whiteAlpha.100'}
_hover={{ _hover={{
transform: 'translateY(-1px)', transform: 'translateY(-1px)',
shadow: 'md', shadow: 'md',
cursor: 'pointer', cursor: 'pointer',
bg: index < 3 ? 'purple.100' : 'gray.100' bg: index < 3 ? 'rgba(168, 85, 247, 0.25)' : 'whiteAlpha.100'
}} }}
transition="all 0.2s" transition="all 0.2s"
onClick={() => onConceptClick?.(null, item.name)} onClick={() => onConceptClick?.(null, item.name)}
@@ -561,8 +562,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
<HStack spacing={3} flex={1}> <HStack spacing={3} flex={1}>
<Box position="relative"> <Box position="relative">
<Badge <Badge
colorScheme={index < 3 ? 'purple' : 'gray'} bg={index < 3 ? 'purple.500' : 'whiteAlpha.200'}
variant="solid" color="white"
borderRadius="full" borderRadius="full"
minW="24px" minW="24px"
h="24px" h="24px"
@@ -581,23 +582,23 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
position="absolute" position="absolute"
top="-8px" top="-8px"
right="-8px" right="-8px"
color="purple.500" color="purple.400"
boxSize={3} boxSize={3}
/> />
)} )}
</Box> </Box>
<VStack align="start" spacing={0} flex={1}> <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} {item.name}
</Text> </Text>
<Text fontSize="xs" color="gray.600"> <Text fontSize="xs" color="whiteAlpha.600">
均幅 {formatChange(item.avg_change)} 均幅 {formatChange(item.avg_change)}
</Text> </Text>
</VStack> </VStack>
</HStack> </HStack>
<Badge <Badge
colorScheme="purple" bg="purple.500"
variant="solid" color="white"
borderRadius="lg" borderRadius="lg"
fontSize="xs" fontSize="xs"
fontWeight="bold" fontWeight="bold"
@@ -621,14 +622,14 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
align="center" align="center"
p={3} p={3}
borderRadius="xl" borderRadius="xl"
bg={index < 3 ? 'cyan.50' : 'gray.50'} bg={index < 3 ? 'rgba(34, 211, 238, 0.15)' : 'whiteAlpha.50'}
border="1px solid" border="1px solid"
borderColor={index < 3 ? 'cyan.100' : 'gray.200'} borderColor={index < 3 ? 'cyan.500' : 'whiteAlpha.100'}
_hover={{ _hover={{
transform: 'translateY(-1px)', transform: 'translateY(-1px)',
shadow: 'md', shadow: 'md',
cursor: 'pointer', cursor: 'pointer',
bg: index < 3 ? 'cyan.100' : 'gray.100' bg: index < 3 ? 'rgba(34, 211, 238, 0.25)' : 'whiteAlpha.100'
}} }}
transition="all 0.2s" transition="all 0.2s"
onClick={() => onConceptClick?.(null, item.name)} onClick={() => onConceptClick?.(null, item.name)}
@@ -636,8 +637,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
<HStack spacing={3} flex={1}> <HStack spacing={3} flex={1}>
<Box position="relative"> <Box position="relative">
<Badge <Badge
colorScheme={index === 0 ? 'cyan' : index < 3 ? 'blue' : 'gray'} bg={index === 0 ? 'cyan.500' : index < 3 ? 'blue.500' : 'whiteAlpha.200'}
variant="solid" color="white"
borderRadius="full" borderRadius="full"
minW="24px" minW="24px"
h="24px" h="24px"
@@ -656,23 +657,23 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
position="absolute" position="absolute"
top="-8px" top="-8px"
right="-8px" right="-8px"
color="cyan.500" color="cyan.400"
boxSize={3} boxSize={3}
/> />
)} )}
</Box> </Box>
<VStack align="start" spacing={0} flex={1}> <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} {item.name}
</Text> </Text>
<Text fontSize="xs" color="gray.600"> <Text fontSize="xs" color="whiteAlpha.600">
累计 {formatChange(item.total_change)} 累计 {formatChange(item.total_change)}
</Text> </Text>
</VStack> </VStack>
</HStack> </HStack>
<Badge <Badge
colorScheme="cyan" bg="cyan.500"
variant="solid" color="white"
borderRadius="lg" borderRadius="lg"
fontSize="xs" fontSize="xs"
fontWeight="bold" fontWeight="bold"
@@ -689,14 +690,17 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
return ( return (
<Box> <Box>
{/* 顶部标题卡片 */} {/* 顶部标题卡片 - 深色玻璃态 */}
<Box <Box
bg="linear-gradient(135deg, #667eea 0%, #764ba2 100%)" bg="rgba(15, 23, 42, 0.8)"
backdropFilter="blur(20px)"
p={4} p={4}
borderRadius="xl" borderRadius="xl"
mb={4} mb={4}
position="relative" position="relative"
overflow="hidden" overflow="hidden"
border="1px solid"
borderColor="whiteAlpha.100"
> >
{/* 背景装饰 */} {/* 背景装饰 */}
<Box <Box
@@ -706,8 +710,9 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
width="80px" width="80px"
height="80px" height="80px"
borderRadius="full" borderRadius="full"
bg="whiteAlpha.200" bg="purple.500"
filter="blur(10px)" opacity={0.2}
filter="blur(20px)"
/> />
<Box <Box
position="absolute" position="absolute"
@@ -716,15 +721,16 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
width="60px" width="60px"
height="60px" height="60px"
borderRadius="full" borderRadius="full"
bg="whiteAlpha.100" bg="cyan.500"
filter="blur(8px)" opacity={0.15}
filter="blur(15px)"
/> />
<VStack align="start" spacing={3} position="relative" w="full"> <VStack align="start" spacing={3} position="relative" w="full">
<Flex justify="space-between" align="center" w="full"> <Flex justify="space-between" align="center" w="full">
<HStack spacing={2}> <HStack spacing={2}>
<Box p={2} bg="whiteAlpha.200" borderRadius="lg"> <Box p={2} bg="whiteAlpha.100" borderRadius="lg" border="1px solid" borderColor="cyan.500">
<Icon as={FaChartLine} color="white" boxSize={4} /> <Icon as={FaChartLine} color="cyan.400" boxSize={4} />
</Box> </Box>
<VStack align="start" spacing={0}> <VStack align="start" spacing={0}>
<Heading size="sm" color="white" fontWeight="bold"> <Heading size="sm" color="white" fontWeight="bold">
@@ -868,8 +874,8 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
</VStack> </VStack>
</Box> </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 <Tabs
index={activeTab} index={activeTab}
onChange={(index) => { onChange={(index) => {
@@ -882,7 +888,7 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
size="sm" size="sm"
> >
<TabList <TabList
bg="gray.50" bg="rgba(15, 23, 42, 0.6)"
borderBottom="1px" borderBottom="1px"
borderColor={borderColor} borderColor={borderColor}
overflowX="auto" overflowX="auto"
@@ -902,6 +908,7 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
px={3} px={3}
py={3} py={3}
whiteSpace="nowrap" whiteSpace="nowrap"
color="whiteAlpha.700"
_selected={{ _selected={{
bg: `${tab.color}.500`, bg: `${tab.color}.500`,
color: 'white', color: 'white',
@@ -917,7 +924,7 @@ const ConceptStatsPanel = ({ apiBaseUrl, onConceptClick }) => {
bg: `${tab.color}.500`, bg: `${tab.color}.500`,
} }
}} }}
_hover={{ bg: `${tab.color}.50` }} _hover={{ bg: 'whiteAlpha.100', color: 'white' }}
transition="all 0.2s" transition="all 0.2s"
> >
<HStack spacing={1}> <HStack spacing={1}>