update pay ui

This commit is contained in:
2025-12-11 08:33:56 +08:00
parent 68c7b6232d
commit 2ebc1cbc97
5 changed files with 927 additions and 214 deletions

View File

@@ -2,7 +2,7 @@
* 概念异动列表组件 - V2 科技感设计
* 展示当日的概念异动记录,点击可展开显示相关股票
*/
import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useEffect } from 'react';
import {
Box,
VStack,
@@ -87,11 +87,10 @@ const getAlertIcon = (alertType) => {
* 指标提示组件 - 带详细说明
*/
const MetricTooltip = ({ metricKey, children }) => {
const tooltipBg = useColorModeValue('gray.800', 'gray.700');
const config = METRIC_CONFIG[metricKey];
if (!config) return children;
const tooltipBg = useColorModeValue('gray.800', 'gray.700');
return (
<Tooltip
label={
@@ -670,7 +669,14 @@ const AlertCard = ({ alert, isExpanded, onToggle, stocks, loadingStocks }) => {
/**
* 概念异动列表
*/
const ConceptAlertList = ({ alerts = [], onAlertClick, selectedAlert, maxHeight = '400px' }) => {
const ConceptAlertList = ({
alerts = [],
onAlertClick,
selectedAlert,
maxHeight = '400px',
autoExpandAlertKey = null,
onAutoExpandComplete,
}) => {
const [expandedId, setExpandedId] = useState(null);
const [conceptStocks, setConceptStocks] = useState({});
const [loadingConcepts, setLoadingConcepts] = useState({});
@@ -678,13 +684,12 @@ const ConceptAlertList = ({ alerts = [], onAlertClick, selectedAlert, maxHeight
const subTextColor = useColorModeValue('gray.500', 'gray.400');
const emptyBg = useColorModeValue('gray.50', '#111111');
// 获取概念相关股票
// 获取概念相关股票 - 使用 ref 避免依赖循环
const fetchConceptStocks = useCallback(async (conceptId) => {
if (conceptStocks[conceptId] || loadingConcepts[conceptId]) {
return;
}
setLoadingConcepts(prev => ({ ...prev, [conceptId]: true }));
setLoadingConcepts(prev => {
if (prev[conceptId]) return prev;
return { ...prev, [conceptId]: true };
});
try {
const response = await axios.get(`/api/concept/${encodeURIComponent(conceptId)}/stocks`);
@@ -702,7 +707,21 @@ const ConceptAlertList = ({ alerts = [], onAlertClick, selectedAlert, maxHeight
} finally {
setLoadingConcepts(prev => ({ ...prev, [conceptId]: false }));
}
}, [conceptStocks, loadingConcepts]);
}, []);
// 处理自动展开
useEffect(() => {
if (autoExpandAlertKey && autoExpandAlertKey !== expandedId) {
setExpandedId(autoExpandAlertKey);
// 找到对应的 alert 并获取股票数据
const [conceptId] = autoExpandAlertKey.split('-');
if (conceptId) {
fetchConceptStocks(conceptId);
}
// 通知父组件自动展开已完成
onAutoExpandComplete?.();
}
}, [autoExpandAlertKey, expandedId, fetchConceptStocks, onAutoExpandComplete]);
// 切换展开状态
const handleToggle = useCallback((alert) => {
@@ -718,7 +737,7 @@ const ConceptAlertList = ({ alerts = [], onAlertClick, selectedAlert, maxHeight
}
onAlertClick?.(alert);
}, [expandedId, fetchConceptStocks, onAlertClick]);
}, [expandedId, fetchConceptStocks, onAlertClick, conceptStocks, loadingConcepts]);
if (!alerts || alerts.length === 0) {
return (

View File

@@ -90,21 +90,28 @@ const IndexMinuteChart = ({ indexData, alerts = [], onAlertClick, height = '350p
// 检查是否有异动
const alertsAtTime = alerts.filter((a) => a.time === time);
if (alertsAtTime.length > 0) {
html += '<div style="border-top: 1px solid #eee; margin-top: 4px; padding-top: 4px;">';
html += '<div style="font-weight: bold; color: #ff6b6b;">概念异动:</div>';
alertsAtTime.forEach((alert) => {
html += '<div style="border-top: 1px solid rgba(139,92,246,0.3); margin-top: 6px; padding-top: 6px;">';
html += `<div style="font-weight: bold; color: #8b5cf6; margin-bottom: 4px;">📍 概念异动 (${alertsAtTime.length})</div>`;
alertsAtTime.slice(0, 5).forEach((alert) => {
const typeLabel = {
surge: '急涨',
surge_up: '涨',
surge_down: '跌',
limit_up: '涨停增加',
surge: '异动',
surge_up: '涨',
surge_down: '跌',
volume_surge_up: '放量急涨',
shrink_surge_up: '缩量急涨',
volume_oscillation: '放量震荡',
limit_up: '涨停潮',
rank_jump: '排名跃升',
volume_spike: '放量',
}[alert.alert_type] || alert.alert_type;
const typeColor = alert.alert_type === 'surge_down' ? '#2ed573' : '#ff6b6b';
const alpha = alert.alpha ? ` (α${alert.alpha > 0 ? '+' : ''}${alert.alpha.toFixed(2)}%)` : '';
html += `<div style="color: ${typeColor}">• ${alert.concept_name} (${typeLabel}${alpha})</div>`;
const typeColor = alert.alert_type === 'surge_down' ? '#52c41a' : '#ff4d4f';
const alpha = alert.alpha ? ` α${alert.alpha > 0 ? '+' : ''}${alert.alpha.toFixed(1)}%` : '';
const score = alert.final_score ? ` [${Math.round(alert.final_score)}分]` : '';
html += `<div style="color: ${typeColor}; font-size: 11px; margin: 2px 0;">• ${alert.concept_name} <span style="opacity:0.8">(${typeLabel}${alpha}${score})</span></div>`;
});
if (alertsAtTime.length > 5) {
html += `<div style="color: #8c8c8c; font-size: 10px; margin-top: 4px;">还有 ${alertsAtTime.length - 5} 个异动...</div>`;
}
html += '</div>';
}
@@ -216,12 +223,19 @@ const IndexMinuteChart = ({ indexData, alerts = [], onAlertClick, height = '350p
chartInstance.current.setOption(chartOption, true);
// 点击事件
// 点击事件 - 支持多个异动
if (onAlertClick) {
chartInstance.current.off('click');
chartInstance.current.on('click', 'series.line.markPoint', (params) => {
if (params.data && params.data.alertData) {
onAlertClick(params.data.alertData);
const alertData = params.data.alertData;
// 如果是数组(多个异动),传递第一个(最高分)
// 调用方可以从 alertData 中获取所有异动
if (Array.isArray(alertData)) {
onAlertClick(alertData[0]);
} else {
onAlertClick(alertData);
}
}
});
}

View File

@@ -1,5 +1,5 @@
/**
* 热点概览组件 - 科技感设计
* 热点概览组件 - Modern Spatial & Glassmorphism 设计
* 展示大盘分时走势 + 概念异动标注
*
* 布局设计:
@@ -20,12 +20,11 @@ import {
Flex,
Spacer,
Tooltip,
useColorModeValue,
IconButton,
Collapse,
SimpleGrid,
} from '@chakra-ui/react';
import { keyframes } from '@emotion/react';
import { keyframes, css } from '@emotion/react';
import {
Flame,
List,
@@ -37,11 +36,19 @@ import {
AlertCircle,
TrendingUp,
TrendingDown,
Sparkles,
} from 'lucide-react';
import { useHotspotData } from './hooks';
import { IndexMinuteChart, ConceptAlertList, AlertSummary } from './components';
import { ALERT_TYPE_CONFIG, getAlertTypeLabel } from './utils/chartHelpers';
import {
glassEffect,
colors,
glowEffects,
getMarketColor,
getMarketGlow,
} from '../../theme/glassTheme';
// 动画效果
const gradientShift = keyframes`
@@ -51,68 +58,94 @@ const gradientShift = keyframes`
`;
const pulseGlow = keyframes`
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
0%, 100% { opacity: 0.6; transform: scale(1); }
50% { opacity: 1; transform: scale(1.02); }
`;
const floatAnimation = keyframes`
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-3px); }
`;
const shimmer = keyframes`
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
`;
/**
* 紧凑型异动卡片(用于横向滚动)
* 紧凑型异动卡片(用于横向滚动)- Glassmorphism 风格
*/
const CompactAlertCard = ({ alert, onClick, isSelected }) => {
const cardBg = useColorModeValue('white', '#0d0d0d');
const borderColor = useColorModeValue('gray.200', '#2d2d2d');
const textColor = useColorModeValue('gray.800', 'white');
const subTextColor = useColorModeValue('gray.500', 'gray.400');
const config = ALERT_TYPE_CONFIG[alert.alert_type] || ALERT_TYPE_CONFIG.surge;
const isUp = alert.alert_type !== 'surge_down';
return (
<Box
bg={cardBg}
borderRadius="xl"
borderWidth="2px"
borderColor={isSelected ? config.color : borderColor}
bg={glassEffect.light.bg}
backdropFilter={glassEffect.light.backdropFilter}
borderRadius="16px"
border={isSelected ? `1px solid ${config.color}60` : glassEffect.light.border}
p={3}
minW="180px"
maxW="200px"
cursor="pointer"
onClick={() => onClick?.(alert)}
transition="all 0.2s"
_hover={{
borderColor: config.color,
transform: 'translateY(-2px)',
boxShadow: `0 4px 15px ${config.color}25`,
}}
transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
position="relative"
overflow="hidden"
_hover={{
bg: 'rgba(255, 255, 255, 0.05)',
border: `1px solid ${config.color}50`,
transform: 'translateY(-4px)',
boxShadow: `0 8px 25px ${config.color}20, inset 0 1px 0 rgba(255,255,255,0.1)`,
}}
css={isSelected ? css`animation: ${floatAnimation} 3s ease-in-out infinite;` : undefined}
>
{/* 顶部渐变条 */}
{/* 顶部渐变发光条 */}
<Box
position="absolute"
top={0}
left={0}
right={0}
h="3px"
h="2px"
bgGradient={`linear(to-r, ${config.gradient[0]}, ${config.gradient[1]})`}
opacity={isSelected ? 1 : 0.7}
boxShadow={isSelected ? `0 0 15px ${config.color}60` : 'none'}
/>
{/* 背景光晕 */}
{isSelected && (
<Box
position="absolute"
top="-30px"
right="-30px"
w="80px"
h="80px"
borderRadius="full"
bg={`${config.color}15`}
filter="blur(25px)"
pointerEvents="none"
/>
)}
{/* 时间 + 类型 */}
<HStack justify="space-between" mb={1}>
<Text fontSize="xs" color={subTextColor} fontFamily="mono">
<HStack justify="space-between" mb={1.5}>
<Text fontSize="xs" color={colors.text.muted} fontFamily="mono">
{alert.time}
</Text>
<HStack
spacing={1}
px={1.5}
px={2}
py={0.5}
borderRadius="md"
bg={`${config.color}15`}
borderRadius="full"
bg={`${config.color}20`}
border={`1px solid ${config.color}30`}
>
<Icon
as={isUp ? TrendingUp : TrendingDown}
boxSize={3}
color={config.color}
css={css`filter: drop-shadow(0 0 4px ${config.color}80);`}
/>
<Text fontSize="10px" fontWeight="bold" color={config.color}>
{getAlertTypeLabel(alert.alert_type)}
@@ -124,9 +157,10 @@ const CompactAlertCard = ({ alert, onClick, isSelected }) => {
<Text
fontWeight="bold"
fontSize="sm"
color={textColor}
color={colors.text.primary}
noOfLines={1}
mb={1}
mb={1.5}
css={isSelected ? css`text-shadow: 0 0 20px ${config.color}40;` : undefined}
>
{alert.concept_name}
</Text>
@@ -134,15 +168,20 @@ const CompactAlertCard = ({ alert, onClick, isSelected }) => {
{/* 分数 + Alpha */}
<HStack justify="space-between" fontSize="xs">
<HStack spacing={1}>
<Text color={subTextColor}>评分</Text>
<Text fontWeight="bold" color={config.color}>
<Text color={colors.text.tertiary}>评分</Text>
<Text
fontWeight="bold"
color={config.color}
css={css`text-shadow: 0 0 10px ${config.color}50;`}
>
{Math.round(alert.final_score || 0)}
</Text>
</HStack>
{alert.alpha != null && (
<Text
fontWeight="bold"
color={alert.alpha >= 0 ? '#ff4d4f' : '#52c41a'}
color={getMarketColor(alert.alpha)}
css={css`text-shadow: 0 0 10px ${getMarketColor(alert.alpha)}50;`}
>
α {alert.alpha >= 0 ? '+' : ''}{alert.alpha.toFixed(1)}%
</Text>
@@ -160,53 +199,76 @@ const CompactAlertCard = ({ alert, onClick, isSelected }) => {
const HotspotOverview = ({ selectedDate }) => {
const [selectedAlert, setSelectedAlert] = useState(null);
const [showDetailList, setShowDetailList] = useState(false);
const [autoExpandAlertKey, setAutoExpandAlertKey] = useState(null);
// 获取数据
const { loading, error, data } = useHotspotData(selectedDate);
// 颜色主题
const cardBg = useColorModeValue('white', '#0a0a0a');
const borderColor = useColorModeValue('gray.200', '#1f1f1f');
const textColor = useColorModeValue('gray.800', 'white');
const subTextColor = useColorModeValue('gray.600', 'gray.400');
const sectionBg = useColorModeValue('gray.50', '#0d0d0d');
const scrollbarColor = useColorModeValue('#ddd', '#333');
// Glassmorphism 颜色主题
const cardBg = glassEffect.card.bg;
const borderColor = colors.border.primary;
const textColor = colors.text.primary;
const subTextColor = colors.text.secondary;
const sectionBg = glassEffect.light.bg;
const scrollbarColor = 'rgba(139, 92, 246, 0.3)';
// 点击异动标注
// 点击异动标注 - 自动展开详细列表并选中
const handleAlertClick = useCallback((alert) => {
setSelectedAlert(alert);
// 自动展开详细列表并设置需要展开的项
setShowDetailList(true);
const alertKey = `${alert.concept_id}-${alert.time}`;
setAutoExpandAlertKey(alertKey);
}, []);
// 渲染加载状态
// 渲染加载状态 - Glassmorphism 风格
if (loading) {
return (
<Box
bg={cardBg}
borderRadius="2xl"
borderWidth="1px"
borderColor={borderColor}
backdropFilter={glassEffect.card.backdropFilter}
borderRadius="24px"
border={glassEffect.card.border}
boxShadow={glassEffect.card.boxShadow}
overflow="hidden"
position="relative"
>
{/* 极光背景 */}
<Box
h="4px"
bgGradient="linear(to-r, orange.400, red.500, pink.500)"
position="absolute"
inset={0}
bgGradient="radial(ellipse at 30% 20%, rgba(139, 92, 246, 0.15) 0%, transparent 50%)"
pointerEvents="none"
/>
{/* 顶部发光条 */}
<Box
h="3px"
bgGradient="linear(to-r, #8b5cf6, #ec4899, #f97316)"
backgroundSize="200% 200%"
css={{ animation: `${gradientShift} 3s ease infinite` }}
css={css`animation: ${gradientShift} 3s ease infinite;`}
boxShadow="0 0 20px rgba(139, 92, 246, 0.5)"
/>
<Center h="500px" p={6}>
<VStack spacing={4}>
<VStack spacing={6}>
<Box position="relative">
<Spinner size="xl" color="orange.400" thickness="3px" speed="0.8s" />
<Spinner size="xl" color="#8b5cf6" thickness="3px" speed="0.8s" />
<Box
position="absolute"
inset={0}
inset={-4}
borderRadius="full"
css={{ animation: `${pulseGlow} 2s ease-in-out infinite` }}
boxShadow="0 0 30px rgba(251, 146, 60, 0.3)"
css={css`animation: ${pulseGlow} 2s ease-in-out infinite;`}
boxShadow="0 0 40px rgba(139, 92, 246, 0.4)"
/>
</Box>
<VStack spacing={1}>
<Text color={textColor} fontWeight="medium">加载热点概览数据</Text>
<VStack spacing={2}>
<Text
color={textColor}
fontWeight="bold"
fontSize="lg"
css={css`text-shadow: 0 0 20px rgba(139, 92, 246, 0.5);`}
>
加载热点概览数据
</Text>
<Text color={subTextColor} fontSize="sm">正在获取市场异动信息...</Text>
</VStack>
</VStack>
@@ -215,24 +277,42 @@ const HotspotOverview = ({ selectedDate }) => {
);
}
// 渲染错误状态
// 渲染错误状态 - Glassmorphism 风格
if (error) {
return (
<Box
bg={cardBg}
borderRadius="2xl"
borderWidth="1px"
borderColor={borderColor}
backdropFilter={glassEffect.card.backdropFilter}
borderRadius="24px"
border="1px solid rgba(239, 68, 68, 0.3)"
boxShadow="0 8px 32px rgba(239, 68, 68, 0.1)"
overflow="hidden"
position="relative"
>
<Box h="4px" bg="red.500" />
<Box h="3px" bg="#ef4444" boxShadow="0 0 15px rgba(239, 68, 68, 0.5)" />
<Center h="400px" p={6}>
<VStack spacing={4}>
<Box p={4} borderRadius="full" bg="red.50">
<Icon as={AlertCircle} boxSize={10} color="red.400" />
<Box
p={4}
borderRadius="full"
bg="rgba(239, 68, 68, 0.1)"
border="1px solid rgba(239, 68, 68, 0.2)"
>
<Icon
as={AlertCircle}
boxSize={10}
color="#ef4444"
css={css`filter: drop-shadow(0 0 10px rgba(239, 68, 68, 0.5));`}
/>
</Box>
<VStack spacing={1}>
<Text color="red.400" fontWeight="medium">数据加载失败</Text>
<Text
color="#ef4444"
fontWeight="bold"
css={css`text-shadow: 0 0 15px rgba(239, 68, 68, 0.5);`}
>
数据加载失败
</Text>
<Text color={subTextColor} fontSize="sm" textAlign="center">{error}</Text>
</VStack>
</VStack>
@@ -245,124 +325,258 @@ const HotspotOverview = ({ selectedDate }) => {
const { index, alerts, alert_summary } = data;
// 计算市场颜色
const marketColor = getMarketColor(index?.change_pct || 0);
const marketGlow = getMarketGlow(index?.change_pct || 0);
return (
<Box
bg={cardBg}
borderRadius="2xl"
borderWidth="1px"
borderColor={borderColor}
backdropFilter={glassEffect.card.backdropFilter}
borderRadius="24px"
border={glassEffect.card.border}
boxShadow={glassEffect.card.boxShadow}
overflow="hidden"
transition="all 0.3s"
transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
position="relative"
>
{/* 顶部装饰 */}
{/* 极光背景装饰 */}
<Box
h="4px"
bgGradient="linear(to-r, orange.400, red.500, pink.500)"
backgroundSize="200% 200%"
css={{ animation: `${gradientShift} 3s ease infinite` }}
position="absolute"
inset={0}
bgGradient="radial(ellipse at 20% 10%, rgba(139, 92, 246, 0.12) 0%, transparent 50%)"
pointerEvents="none"
/>
<Box
position="absolute"
inset={0}
bgGradient="radial(ellipse at 80% 90%, rgba(236, 72, 153, 0.08) 0%, transparent 50%)"
pointerEvents="none"
/>
<Box p={5}>
{/* 头部 */}
<Flex align="center" mb={4}>
<HStack spacing={3}>
{/* 顶部发光装饰条 */}
<Box
h="3px"
bgGradient="linear(to-r, #8b5cf6, #ec4899, #f97316)"
backgroundSize="200% 200%"
css={css`animation: ${gradientShift} 3s ease infinite;`}
boxShadow="0 0 20px rgba(139, 92, 246, 0.5)"
/>
<Box p={6} position="relative">
{/* 头部 - Glassmorphism */}
<Flex align="center" mb={5}>
<HStack spacing={4}>
<Box
p={2}
borderRadius="xl"
bgGradient="linear(to-br, orange.400, red.500)"
boxShadow="0 4px 15px rgba(251, 146, 60, 0.4)"
p={3}
borderRadius="16px"
bgGradient="linear(to-br, #8b5cf6, #ec4899)"
boxShadow="0 8px 25px rgba(139, 92, 246, 0.4)"
position="relative"
overflow="hidden"
>
<Icon as={Flame} boxSize={5} color="white" />
{/* 图标发光效果 */}
<Box
position="absolute"
inset={0}
bgGradient="linear(to-br, rgba(255,255,255,0.2), transparent)"
/>
<Icon
as={Flame}
boxSize={6}
color="white"
css={css`filter: drop-shadow(0 0 8px rgba(255,255,255,0.5));`}
/>
</Box>
<VStack align="flex-start" spacing={0}>
<Heading size="md" color={textColor} fontWeight="bold">热点概览</Heading>
<Text fontSize="xs" color={subTextColor}>实时概念异动监控</Text>
<Heading
size="md"
color={textColor}
fontWeight="bold"
css={css`text-shadow: 0 0 30px rgba(139, 92, 246, 0.3);`}
>
热点概览
</Heading>
<HStack spacing={1}>
<Icon as={Sparkles} boxSize={3} color={colors.accent.purple} />
<Text fontSize="xs" color={subTextColor}>实时概念异动监控</Text>
</HStack>
</VStack>
</HStack>
<Spacer />
<HStack spacing={2}>
<HStack spacing={3}>
{alerts.length > 0 && (
<HStack spacing={1} px={3} py={1.5} borderRadius="full" bg="orange.50">
<Icon as={Zap} boxSize={3.5} color="orange.500" />
<Text fontSize="sm" fontWeight="bold" color="orange.500">{alerts.length}</Text>
<HStack
spacing={2}
px={4}
py={2}
borderRadius="full"
bg="rgba(139, 92, 246, 0.15)"
border="1px solid rgba(139, 92, 246, 0.3)"
boxShadow="0 0 15px rgba(139, 92, 246, 0.2)"
>
<Icon
as={Zap}
boxSize={4}
color={colors.accent.purple}
css={css`filter: drop-shadow(0 0 6px #8b5cf6);`}
/>
<Text
fontSize="sm"
fontWeight="bold"
color={colors.accent.purple}
css={css`text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);`}
>
{alerts.length}
</Text>
</HStack>
)}
<Tooltip label="展示大盘走势与概念异动的关联" hasArrow maxW="200px">
<Box cursor="help">
<Box cursor="help" p={2} borderRadius="full" _hover={{ bg: 'rgba(255,255,255,0.05)' }}>
<Icon as={Info} color={subTextColor} boxSize={4} />
</Box>
</Tooltip>
</HStack>
</Flex>
{/* 统计摘要 - 简化版 */}
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4} mb={4}>
{/* 指数信息 */}
<Box bg={sectionBg} borderRadius="xl" p={4} borderWidth="1px" borderColor={borderColor}>
{/* 统计摘要 - Glassmorphism Bento Grid */}
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4} mb={5}>
{/* 指数信息卡片 */}
<Box
bg={sectionBg}
backdropFilter={glassEffect.light.backdropFilter}
borderRadius="20px"
border={glassEffect.light.border}
p={5}
position="relative"
overflow="hidden"
transition="all 0.3s"
_hover={{
border: `1px solid ${marketColor}30`,
boxShadow: `0 8px 30px ${marketColor}15`,
}}
>
{/* 背景光晕 */}
<Box
position="absolute"
top="-20px"
right="-20px"
w="100px"
h="100px"
borderRadius="full"
bg={`${marketColor}10`}
filter="blur(30px)"
pointerEvents="none"
/>
<HStack justify="space-between" align="flex-start">
<VStack align="flex-start" spacing={0}>
<Text fontSize="xs" color={subTextColor}>{index?.name || '上证指数'}</Text>
<VStack align="flex-start" spacing={1}>
<Text fontSize="xs" color={colors.text.muted} letterSpacing="1px" textTransform="uppercase">
{index?.name || '上证指数'}
</Text>
<Text
fontSize="2xl"
fontSize="3xl"
fontWeight="bold"
color={(index?.change_pct || 0) >= 0 ? '#ff4d4f' : '#52c41a'}
color={marketColor}
css={css`text-shadow: 0 0 30px ${marketColor}60;`}
>
{index?.latest_price?.toFixed(2) || '-'}
</Text>
</VStack>
<VStack align="flex-end" spacing={0}>
<VStack align="flex-end" spacing={2}>
<HStack
spacing={1}
px={2}
py={1}
spacing={2}
px={3}
py={1.5}
borderRadius="full"
bg={(index?.change_pct || 0) >= 0 ? 'red.50' : 'green.50'}
bg={`${marketColor}15`}
border={`1px solid ${marketColor}25`}
>
<Icon
as={(index?.change_pct || 0) >= 0 ? TrendingUp : TrendingDown}
boxSize={3}
color={(index?.change_pct || 0) >= 0 ? '#ff4d4f' : '#52c41a'}
boxSize={4}
color={marketColor}
css={css`filter: drop-shadow(0 0 4px ${marketColor});`}
/>
<Text
fontSize="sm"
fontWeight="bold"
color={(index?.change_pct || 0) >= 0 ? '#ff4d4f' : '#52c41a'}
color={marketColor}
css={css`text-shadow: 0 0 10px ${marketColor}50;`}
>
{(index?.change_pct || 0) >= 0 ? '+' : ''}{(index?.change_pct || 0).toFixed(2)}%
</Text>
</HStack>
<HStack spacing={3} mt={1} fontSize="xs" color={subTextColor}>
<Text> <Text as="span" color="#ff4d4f" fontWeight="bold">{index?.high?.toFixed(2)}</Text></Text>
<Text> <Text as="span" color="#52c41a" fontWeight="bold">{index?.low?.toFixed(2)}</Text></Text>
<HStack spacing={4} fontSize="xs" color={colors.text.tertiary}>
<HStack spacing={1}>
<Text></Text>
<Text color={colors.market.up} fontWeight="bold">{index?.high?.toFixed(2)}</Text>
</HStack>
<HStack spacing={1}>
<Text></Text>
<Text color={colors.market.down} fontWeight="bold">{index?.low?.toFixed(2)}</Text>
</HStack>
</HStack>
</VStack>
</HStack>
</Box>
{/* 异动统计 */}
<Box bg={sectionBg} borderRadius="xl" p={4} borderWidth="1px" borderColor={borderColor}>
<HStack justify="space-between" mb={2}>
<Text fontSize="sm" fontWeight="medium" color={textColor}>今日异动</Text>
<Text fontSize="xs" color="orange.500" fontWeight="bold">{alerts.length} </Text>
{/* 异动统计卡片 */}
<Box
bg={sectionBg}
backdropFilter={glassEffect.light.backdropFilter}
borderRadius="20px"
border={glassEffect.light.border}
p={5}
position="relative"
overflow="hidden"
transition="all 0.3s"
_hover={{
border: '1px solid rgba(139, 92, 246, 0.3)',
boxShadow: '0 8px 30px rgba(139, 92, 246, 0.1)',
}}
>
<HStack justify="space-between" mb={3}>
<Text fontSize="sm" fontWeight="bold" color={textColor}>今日异动</Text>
<Text
fontSize="sm"
color={colors.accent.purple}
fontWeight="bold"
css={css`text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);`}
>
{alerts.length}
</Text>
</HStack>
<Flex gap={2} flexWrap="wrap">
{Object.entries(alert_summary || {})
.filter(([_, count]) => count > 0)
.slice(0, 4)
.slice(0, 5)
.map(([type, count]) => {
const config = ALERT_TYPE_CONFIG[type];
if (!config) return null;
return (
<HStack
key={type}
spacing={1}
px={2}
py={1}
borderRadius="md"
bg={`${config.color}10`}
spacing={1.5}
px={3}
py={1.5}
borderRadius="full"
bg={`${config.color}15`}
border={`1px solid ${config.color}25`}
transition="all 0.2s"
_hover={{
bg: `${config.color}25`,
boxShadow: `0 0 15px ${config.color}30`,
}}
>
<Text fontSize="xs" color={config.color}>{config.label}</Text>
<Text fontSize="xs" fontWeight="bold" color={config.color}>{count}</Text>
<Text
fontSize="xs"
fontWeight="bold"
color={config.color}
css={css`text-shadow: 0 0 8px ${config.color}50;`}
>
{count}
</Text>
</HStack>
);
})}
@@ -370,22 +584,54 @@ const HotspotOverview = ({ selectedDate }) => {
</Box>
</SimpleGrid>
{/* 大尺寸分时图 */}
{/* 大尺寸分时图 - Glassmorphism */}
<Box
bg={sectionBg}
borderRadius="xl"
borderWidth="1px"
borderColor={borderColor}
p={4}
mb={4}
backdropFilter={glassEffect.light.backdropFilter}
borderRadius="20px"
border={glassEffect.light.border}
p={5}
mb={5}
position="relative"
overflow="hidden"
>
<HStack spacing={2} mb={3}>
<Box p={1.5} borderRadius="lg" bg="purple.50">
<Icon as={LineChart} boxSize={4} color="purple.500" />
{/* 图表区域背景光晕 */}
<Box
position="absolute"
bottom="-50px"
left="50%"
transform="translateX(-50%)"
w="60%"
h="100px"
borderRadius="full"
bg="rgba(139, 92, 246, 0.08)"
filter="blur(40px)"
pointerEvents="none"
/>
<HStack spacing={3} mb={4}>
<Box
p={2}
borderRadius="12px"
bg="rgba(139, 92, 246, 0.15)"
border="1px solid rgba(139, 92, 246, 0.25)"
>
<Icon
as={LineChart}
boxSize={5}
color={colors.accent.purple}
css={css`filter: drop-shadow(0 0 6px #8b5cf6);`}
/>
</Box>
<Text fontSize="sm" fontWeight="bold" color={textColor}>大盘分时走势</Text>
<Text
fontSize="sm"
fontWeight="bold"
color={textColor}
css={css`text-shadow: 0 0 20px rgba(139, 92, 246, 0.3);`}
>
大盘分时走势
</Text>
<Tooltip label="图表上的标记点表示概念异动时刻,点击可查看详情" hasArrow>
<Icon as={Info} boxSize={3} color={subTextColor} cursor="help" />
<Icon as={Info} boxSize={3.5} color={colors.text.muted} cursor="help" />
</Tooltip>
</HStack>
<IndexMinuteChart
@@ -396,23 +642,38 @@ const HotspotOverview = ({ selectedDate }) => {
/>
</Box>
{/* 异动列表 - 横向滚动 */}
{/* 异动列表 - Glassmorphism 横向滚动 */}
{alerts.length > 0 && (
<Box>
<Flex justify="space-between" align="center" mb={3}>
<HStack spacing={2}>
<Box p={1.5} borderRadius="lg" bg="orange.50">
<Icon as={List} boxSize={4} color="orange.500" />
<Flex justify="space-between" align="center" mb={4}>
<HStack spacing={3}>
<Box
p={2}
borderRadius="12px"
bg="rgba(249, 115, 22, 0.15)"
border="1px solid rgba(249, 115, 22, 0.25)"
>
<Icon
as={List}
boxSize={5}
color={colors.accent.orange}
css={css`filter: drop-shadow(0 0 6px #f97316);`}
/>
</Box>
<Text fontSize="sm" fontWeight="bold" color={textColor}>异动记录</Text>
<Text fontSize="xs" color={subTextColor}>横向滚动查看更多</Text>
<Text fontSize="xs" color={colors.text.muted}>点击卡片查看个股详情</Text>
</HStack>
<Tooltip label={showDetailList ? '收起详细列表' : '展开详细列表'} hasArrow>
<IconButton
icon={<Icon as={showDetailList ? ChevronUp : ChevronDown} boxSize={4} />}
size="sm"
variant="ghost"
borderRadius="lg"
borderRadius="12px"
color={colors.text.secondary}
_hover={{
bg: 'rgba(255,255,255,0.05)',
color: textColor,
}}
onClick={() => setShowDetailList(!showDetailList)}
aria-label="切换详细列表"
/>
@@ -422,13 +683,14 @@ const HotspotOverview = ({ selectedDate }) => {
{/* 横向滚动卡片 */}
<Box
overflowX="auto"
pb={2}
pb={3}
sx={{
'&::-webkit-scrollbar': { height: '6px' },
'&::-webkit-scrollbar-track': { background: 'transparent' },
'&::-webkit-scrollbar-track': { background: 'rgba(255,255,255,0.02)', borderRadius: '3px' },
'&::-webkit-scrollbar-thumb': {
background: scrollbarColor,
borderRadius: '3px',
'&:hover': { background: 'rgba(139, 92, 246, 0.5)' },
},
}}
>
@@ -446,33 +708,84 @@ const HotspotOverview = ({ selectedDate }) => {
</HStack>
</Box>
{/* 详细列表(可展开) */}
{/* 详细列表(可展开) - Glassmorphism */}
<Collapse in={showDetailList} animateOpacity>
<Box
mt={4}
bg={sectionBg}
borderRadius="xl"
borderWidth="1px"
borderColor={borderColor}
p={4}
backdropFilter={glassEffect.light.backdropFilter}
borderRadius="20px"
border={glassEffect.light.border}
p={5}
position="relative"
overflow="hidden"
>
{/* 背景光晕 */}
<Box
position="absolute"
top="50%"
left="50%"
transform="translate(-50%, -50%)"
w="80%"
h="200px"
borderRadius="full"
bg="rgba(139, 92, 246, 0.05)"
filter="blur(60px)"
pointerEvents="none"
/>
<ConceptAlertList
alerts={alerts}
onAlertClick={handleAlertClick}
selectedAlert={selectedAlert}
maxHeight="400px"
autoExpandAlertKey={autoExpandAlertKey}
onAutoExpandComplete={() => setAutoExpandAlertKey(null)}
/>
</Box>
</Collapse>
</Box>
)}
{/* 无异动提示 */}
{/* 无异动提示 - Glassmorphism */}
{alerts.length === 0 && (
<Center py={8} bg={sectionBg} borderRadius="xl">
<VStack spacing={2}>
<Icon as={Zap} boxSize={8} color={subTextColor} opacity={0.5} />
<Text color={subTextColor} fontSize="sm">当日暂无概念异动数据</Text>
<Center
py={12}
bg={sectionBg}
backdropFilter={glassEffect.light.backdropFilter}
borderRadius="20px"
border={glassEffect.light.border}
position="relative"
overflow="hidden"
>
{/* 背景光晕 */}
<Box
position="absolute"
top="50%"
left="50%"
transform="translate(-50%, -50%)"
w="150px"
h="150px"
borderRadius="full"
bg="rgba(139, 92, 246, 0.1)"
filter="blur(40px)"
pointerEvents="none"
/>
<VStack spacing={3}>
<Box
p={4}
borderRadius="full"
bg="rgba(139, 92, 246, 0.1)"
border="1px solid rgba(139, 92, 246, 0.2)"
>
<Icon
as={Zap}
boxSize={8}
color={colors.accent.purple}
opacity={0.6}
css={css`filter: drop-shadow(0 0 10px rgba(139, 92, 246, 0.3));`}
/>
</Box>
<Text color={colors.text.tertiary} fontSize="sm">当日暂无概念异动数据</Text>
</VStack>
</Center>
)}

View File

@@ -281,53 +281,96 @@ export const getAlertTypeColor = (alertType) => {
};
/**
* 生成图表标注点数据
* 生成图表标注点数据 - 支持同一时间多个异动折叠显示
* @param {Array} alerts - 异动数据数组
* @param {Array} times - 时间数组
* @param {Array} prices - 价格数组
* @param {number} priceMax - 最高价格(用于无法匹配时间时的默认位置)
* @param {number} maxCount - 最大显示数量
* @param {number} maxTimePoints - 最大显示的时间点数量
* @returns {Array} ECharts markPoint data
*/
export const getAlertMarkPoints = (alerts, times, prices, priceMax, maxCount = 15) => {
export const getAlertMarkPoints = (alerts, times, prices, priceMax, maxTimePoints = 20) => {
if (!alerts || alerts.length === 0) return [];
// 按重要性排序,限制显示数量
const sortedAlerts = [...alerts]
.sort((a, b) => (b.final_score || b.importance_score || 0) - (a.final_score || a.importance_score || 0))
.slice(0, maxCount);
// 1. 按时间分组异动
const alertsByTime = {};
alerts.forEach(alert => {
const time = alert.time;
if (!alertsByTime[time]) {
alertsByTime[time] = [];
}
alertsByTime[time].push(alert);
});
return sortedAlerts.map((alert) => {
// 找到对应时间的价格
const timeIndex = times.indexOf(alert.time);
const price = timeIndex >= 0 ? prices[timeIndex] : (alert.index_price || priceMax);
// 2. 对每个时间点的异动按重要性排序
Object.keys(alertsByTime).forEach(time => {
alertsByTime[time].sort((a, b) =>
(b.final_score || b.importance_score || 0) - (a.final_score || a.importance_score || 0)
);
});
// 3. 按时间点的最高分排序,限制数量
const sortedTimePoints = Object.entries(alertsByTime)
.map(([time, timeAlerts]) => ({
time,
alerts: timeAlerts,
maxScore: Math.max(...timeAlerts.map(a => a.final_score || a.importance_score || 0)),
}))
.sort((a, b) => b.maxScore - a.maxScore)
.slice(0, maxTimePoints);
// 4. 生成标记点数据
return sortedTimePoints.map(({ time, alerts: timeAlerts }) => {
const timeIndex = times.indexOf(time);
const price = timeIndex >= 0 ? prices[timeIndex] : priceMax;
const alertCount = timeAlerts.length;
const topAlert = timeAlerts[0]; // 最高分的异动
const hasMultiple = alertCount > 1;
// 使用最高分异动的样式
const { color, gradient, symbol, symbolSize } = getAlertStyle(
alert.alert_type,
alert.final_score / 100 || alert.importance_score || 0.5
topAlert.alert_type,
topAlert.final_score / 100 || topAlert.importance_score || 0.5
);
// 格式化标签
let label = alert.concept_name || '';
if (label.length > 6) {
label = label.substring(0, 5) + '...';
// 生成标签
let label;
if (hasMultiple) {
// 多个异动时显示数量和最高分概念
const topName = topAlert.concept_name || '';
const shortName = topName.length > 4 ? topName.substring(0, 3) + '..' : topName;
label = `${shortName} +${alertCount - 1}`;
} else {
// 单个异动显示概念名称
label = topAlert.concept_name || '';
if (label.length > 6) {
label = label.substring(0, 5) + '..';
}
}
// 添加涨停数量(如果有)
if (alert.limit_up_count > 0) {
label += `\n涨停: ${alert.limit_up_count}`;
}
const isDown = topAlert.alert_type === 'surge_down';
const isDown = alert.alert_type === 'surge_down';
// 多个异动时使用更醒目的样式
const finalSymbolSize = hasMultiple ? symbolSize + 8 : symbolSize;
const borderWidth = hasMultiple ? 3 : 2;
return {
name: alert.concept_name,
coord: [alert.time, price],
name: hasMultiple ? `${time} (${alertCount}个异动)` : topAlert.concept_name,
coord: [time, price],
value: label,
symbol,
symbolSize,
symbol: hasMultiple ? 'pin' : symbol, // 多个异动用 pin 图标
symbolSize: finalSymbolSize,
itemStyle: {
color: {
color: hasMultiple ? {
type: 'radial',
x: 0.5, y: 0.5, r: 0.8,
colorStops: [
{ offset: 0, color: gradient[0] },
{ offset: 0.7, color: gradient[1] },
{ offset: 1, color: `${color}88` },
],
} : {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
@@ -335,27 +378,37 @@ export const getAlertMarkPoints = (alerts, times, prices, priceMax, maxCount = 1
{ offset: 1, color: gradient[1] },
],
},
borderColor: 'rgba(255,255,255,0.8)',
borderWidth: 2,
shadowBlur: 8,
shadowColor: `${color}66`,
borderColor: hasMultiple ? '#ffffff' : 'rgba(255,255,255,0.8)',
borderWidth,
shadowBlur: hasMultiple ? 15 : 8,
shadowColor: `${color}${hasMultiple ? '99' : '66'}`,
},
label: {
show: true,
position: isDown ? 'bottom' : 'top',
formatter: '{b}',
fontSize: 10,
fontWeight: 500,
formatter: hasMultiple ? `{b|${label}}` : '{b}',
fontSize: hasMultiple ? 11 : 10,
fontWeight: hasMultiple ? 700 : 500,
color: isDown ? '#52c41a' : '#ff4d4f',
backgroundColor: 'rgba(255,255,255,0.95)',
padding: [3, 6],
borderRadius: 4,
backgroundColor: hasMultiple ? 'rgba(255,255,255,0.98)' : 'rgba(255,255,255,0.95)',
padding: hasMultiple ? [4, 8] : [3, 6],
borderRadius: hasMultiple ? 6 : 4,
borderColor: color,
borderWidth: 1,
shadowBlur: 4,
shadowColor: 'rgba(0,0,0,0.1)',
borderWidth: hasMultiple ? 2 : 1,
shadowBlur: hasMultiple ? 8 : 4,
shadowColor: hasMultiple ? `${color}40` : 'rgba(0,0,0,0.1)',
rich: hasMultiple ? {
b: {
fontSize: 11,
fontWeight: 700,
color: isDown ? '#52c41a' : '#ff4d4f',
},
} : undefined,
},
alertData: alert, // 存储原始数据
// 存储所有该时间点的异动数据
alertData: hasMultiple ? timeAlerts : topAlert,
alertCount,
time,
};
});
};

View File

@@ -0,0 +1,314 @@
/**
* Glassmorphism 主题系统 - Modern Spatial Design
*
* 设计理念:打造漂浮在深空中的半透明玻璃态数据终端
* 强调光影深度、弥散背景光和极致圆角
*/
// 极光背景渐变配置
export const auroraGradients = {
// 主背景 - 深空极光
primary: `
radial-gradient(ellipse at 20% 0%, rgba(120, 119, 198, 0.15) 0%, transparent 50%),
radial-gradient(ellipse at 80% 0%, rgba(74, 144, 226, 0.12) 0%, transparent 50%),
radial-gradient(ellipse at 40% 100%, rgba(131, 58, 180, 0.1) 0%, transparent 50%),
radial-gradient(ellipse at 60% 50%, rgba(29, 78, 216, 0.08) 0%, transparent 50%),
linear-gradient(180deg, #0a0a0f 0%, #0d0d14 50%, #0a0a0f 100%)
`,
// 轻量版 - 用于嵌套组件
light: `
radial-gradient(ellipse at 50% 0%, rgba(120, 119, 198, 0.08) 0%, transparent 70%),
linear-gradient(180deg, rgba(15, 15, 25, 0.95) 0%, rgba(10, 10, 15, 0.98) 100%)
`,
// 高亮版 - 用于活跃状态
highlight: `
radial-gradient(ellipse at 50% 50%, rgba(139, 92, 246, 0.2) 0%, transparent 60%),
radial-gradient(ellipse at 30% 80%, rgba(59, 130, 246, 0.15) 0%, transparent 50%)
`,
};
// 毛玻璃效果配置
export const glassEffect = {
// 标准玻璃卡片
card: {
bg: 'rgba(255, 255, 255, 0.03)',
backdropFilter: 'blur(20px) saturate(180%)',
border: '1px solid rgba(255, 255, 255, 0.08)',
borderRadius: '24px',
boxShadow: `
0 8px 32px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.05)
`,
},
// 轻量玻璃 - 用于内部元素
light: {
bg: 'rgba(255, 255, 255, 0.02)',
backdropFilter: 'blur(12px)',
border: '1px solid rgba(255, 255, 255, 0.05)',
borderRadius: '16px',
},
// 强调玻璃 - 用于重要元素
accent: {
bg: 'rgba(139, 92, 246, 0.1)',
backdropFilter: 'blur(16px) saturate(200%)',
border: '1px solid rgba(139, 92, 246, 0.2)',
borderRadius: '20px',
boxShadow: '0 4px 24px rgba(139, 92, 246, 0.15)',
},
// 悬浮玻璃 - 用于弹出层
floating: {
bg: 'rgba(20, 20, 30, 0.9)',
backdropFilter: 'blur(24px) saturate(200%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '20px',
boxShadow: `
0 25px 50px -12px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.05)
`,
},
};
// 发光效果配置
export const glowEffects = {
// 文字发光
textGlow: (color = '#8b5cf6') => ({
textShadow: `0 0 20px ${color}60, 0 0 40px ${color}30`,
}),
// 边框发光
borderGlow: (color = '#8b5cf6') => ({
boxShadow: `0 0 20px ${color}30, inset 0 0 20px ${color}10`,
}),
// 图标发光
iconGlow: (color = '#8b5cf6') => ({
filter: `drop-shadow(0 0 8px ${color}80)`,
}),
// 数据发光 - 用于重要数值
dataGlow: {
up: { textShadow: '0 0 20px rgba(239, 68, 68, 0.5)' },
down: { textShadow: '0 0 20px rgba(34, 197, 94, 0.5)' },
neutral: { textShadow: '0 0 15px rgba(148, 163, 184, 0.3)' },
},
};
// 颜色系统
export const colors = {
// 主要颜色
primary: {
50: 'rgba(139, 92, 246, 0.1)',
100: 'rgba(139, 92, 246, 0.2)',
200: 'rgba(139, 92, 246, 0.3)',
300: '#a78bfa',
400: '#8b5cf6',
500: '#7c3aed',
},
// 背景色
background: {
primary: '#0a0a0f',
secondary: '#0d0d14',
tertiary: '#12121a',
card: 'rgba(255, 255, 255, 0.03)',
hover: 'rgba(255, 255, 255, 0.06)',
},
// 文字颜色
text: {
primary: 'rgba(255, 255, 255, 0.95)',
secondary: 'rgba(255, 255, 255, 0.7)',
tertiary: 'rgba(255, 255, 255, 0.5)',
muted: 'rgba(255, 255, 255, 0.35)',
},
// 边框颜色
border: {
primary: 'rgba(255, 255, 255, 0.08)',
secondary: 'rgba(255, 255, 255, 0.05)',
accent: 'rgba(139, 92, 246, 0.3)',
},
// 涨跌颜色
market: {
up: '#ef4444',
upGlow: 'rgba(239, 68, 68, 0.5)',
upBg: 'rgba(239, 68, 68, 0.1)',
down: '#22c55e',
downGlow: 'rgba(34, 197, 94, 0.5)',
downBg: 'rgba(34, 197, 94, 0.1)',
neutral: '#94a3b8',
},
// 强调色
accent: {
purple: '#8b5cf6',
blue: '#3b82f6',
cyan: '#06b6d4',
pink: '#ec4899',
orange: '#f97316',
},
};
// 动画配置
export const animations = {
// 极光动画 CSS
auroraKeyframes: `
@keyframes aurora {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
`,
// 呼吸发光
breatheKeyframes: `
@keyframes breathe {
0%, 100% {
opacity: 0.5;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.02);
}
}
`,
// 浮动
floatKeyframes: `
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-5px);
}
}
`,
};
// 交互效果
export const interactions = {
// 卡片悬浮效果
cardHover: {
transform: 'translateY(-4px)',
boxShadow: `
0 20px 40px rgba(0, 0, 0, 0.4),
0 0 30px rgba(139, 92, 246, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.1)
`,
borderColor: 'rgba(139, 92, 246, 0.3)',
},
// 按钮悬浮
buttonHover: {
transform: 'translateY(-2px)',
boxShadow: '0 10px 20px rgba(139, 92, 246, 0.2)',
},
// 列表项悬浮
listItemHover: {
bg: 'rgba(255, 255, 255, 0.05)',
borderColor: 'rgba(255, 255, 255, 0.1)',
},
};
// 预设样式组合 - 可直接用于 Chakra sx prop
export const presets = {
// 页面容器
pageContainer: {
minH: '100vh',
bg: colors.background.primary,
backgroundImage: auroraGradients.primary,
backgroundAttachment: 'fixed',
color: colors.text.primary,
},
// 主卡片
glassCard: {
...glassEffect.card,
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
_hover: interactions.cardHover,
},
// 内容区块
contentSection: {
...glassEffect.light,
p: 5,
},
// 标题样式
sectionTitle: {
fontSize: 'lg',
fontWeight: 'bold',
color: colors.text.primary,
...glowEffects.textGlow(colors.accent.purple),
letterSpacing: '0.5px',
},
// 数据标签
dataLabel: {
fontSize: 'xs',
color: colors.text.tertiary,
textTransform: 'uppercase',
letterSpacing: '1px',
},
// 上涨数值
valueUp: {
color: colors.market.up,
fontWeight: 'bold',
...glowEffects.dataGlow.up,
},
// 下跌数值
valueDown: {
color: colors.market.down,
fontWeight: 'bold',
...glowEffects.dataGlow.down,
},
};
// 指标提示样式
export const tooltipStyles = {
container: {
bg: 'rgba(15, 15, 25, 0.95)',
backdropFilter: 'blur(16px)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.5)',
p: 3,
},
title: {
color: colors.accent.purple,
fontWeight: 'bold',
fontSize: 'sm',
mb: 1,
},
description: {
color: colors.text.secondary,
fontSize: 'xs',
lineHeight: 1.5,
},
};
// 工具函数:获取涨跌颜色
export const getMarketColor = (value) => {
if (value > 0) return colors.market.up;
if (value < 0) return colors.market.down;
return colors.market.neutral;
};
// 工具函数:获取涨跌背景
export const getMarketBg = (value) => {
if (value > 0) return colors.market.upBg;
if (value < 0) return colors.market.downBg;
return 'rgba(148, 163, 184, 0.1)';
};
// 工具函数:获取涨跌发光
export const getMarketGlow = (value) => {
if (value > 0) return glowEffects.dataGlow.up;
if (value < 0) return glowEffects.dataGlow.down;
return glowEffects.dataGlow.neutral;
};
export default {
auroraGradients,
glassEffect,
glowEffects,
colors,
animations,
interactions,
presets,
tooltipStyles,
getMarketColor,
getMarketBg,
getMarketGlow,
};