refactor(StockOverview): 迁移至 HeroSection 组件

- 使用通用 HeroSection 替换原有 Hero 区域代码
- 配置 purple 主题预设和自定义金色渐变
- 统计区显示市值、成交额、上涨/下跌家数
- 搜索框支持下拉结果选择

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-30 11:11:32 +08:00
parent 6c10d420a1
commit bc6d5fd222

View File

@@ -53,6 +53,7 @@ import ConceptStocksModal from '@components/ConceptStocksModal';
import TradeDatePicker from '@components/TradeDatePicker';
import HotspotOverview from './components/HotspotOverview';
import FlexScreen from './components/FlexScreen';
import { HeroSection } from '@components/HeroSection';
import { echarts } from '@lib/echarts';
import { logger } from '../../utils/logger';
import { getConceptHtmlUrl } from '../../utils/textUtils';
@@ -623,240 +624,83 @@ const StockOverview = () => {
zIndex={0}
/>
{/* Hero Section */}
<Box
position="relative"
bgGradient={heroBg}
color="white"
overflow="visible"
pt={{ base: 20, md: 24 }}
pb={{ base: 16, md: 20 }}
borderBottom={`1px solid rgba(139, 92, 246, 0.3)`}
borderRadius="xl"
zIndex={1}
>
{/* 背景装饰 */}
<Box
position="absolute"
top="-20%"
right="-10%"
width="40%"
height="120%"
bg={`${goldColor}15`}
transform="rotate(12deg)"
borderRadius="full"
filter="blur(60px)"
/>
<Box px={6} position="relative">
<VStack spacing={8} align="center">
<VStack spacing={4} textAlign="center" maxW="3xl">
<HStack spacing={3}>
<Icon as={TrendingUp} boxSize={12} color={colorMode === 'dark' ? goldColor : 'white'} />
<Heading
as="h1"
size="2xl"
fontWeight="bold"
bgGradient={colorMode === 'dark' ? `linear(to-r, ${goldColor}, white)` : 'none'}
bgClip={colorMode === 'dark' ? 'text' : 'none'}
>
个股中心
</Heading>
</HStack>
<Text fontSize="xl" opacity={0.9} color={colorMode === 'dark' ? 'gray.300' : 'white'}>
实时追踪市场动态洞察投资机会
</Text>
</VStack>
{/* 搜索框 */}
<Box w="100%" maxW="2xl" position="relative">
<InputGroup
size="lg"
bg={searchBg}
borderRadius="full"
boxShadow="2xl"
border="2px solid"
borderColor={colorMode === 'dark' ? goldColor : 'transparent'}
>
<InputLeftElement pointerEvents="none">
<Search size={16} color={colorMode === 'dark' ? goldColor : 'gray.400'} />
</InputLeftElement>
<Input
placeholder="搜索股票代码、名称或拼音首字母..."
value={searchQuery}
onChange={handleSearchChange}
borderRadius="full"
border="none"
color={textColor}
bg="transparent"
_placeholder={{ color: colorMode === 'dark' ? 'gray.500' : 'gray.400' }}
_focus={{
boxShadow: 'none',
borderColor: 'transparent',
bg: colorMode === 'dark' ? 'whiteAlpha.50' : 'transparent'
}}
pr={searchQuery ? "3rem" : "1rem"}
/>
{searchQuery && (
<InputRightElement>
<IconButton
size="sm"
icon={<X size={16} />}
variant="ghost"
onClick={handleClearSearch}
aria-label="清空搜索"
color={colorMode === 'dark' ? goldColor : 'gray.600'}
_hover={{
bg: colorMode === 'dark' ? 'whiteAlpha.100' : 'gray.100'
}}
/>
</InputRightElement>
)}
</InputGroup>
{/* 搜索结果下拉 */}
<Collapse in={showResults} animateOpacity>
<Box
position="absolute"
top="100%"
left={0}
right={0}
mt={2}
bg={searchBg}
borderRadius="xl"
boxShadow="2xl"
border="1px solid"
borderColor={borderColor}
maxH="400px"
overflowY="auto"
zIndex={10}
>
{isSearching ? (
<Center p={4}>
<Spinner color={accentColor} />
</Center>
) : searchResults.length > 0 ? (
<List spacing={0}>
{searchResults.map((stock, index) => (
<ListItem
key={stock.stock_code}
p={4}
cursor="pointer"
_hover={{ bg: hoverBg }}
onClick={() => handleSelectStock(stock, index)}
borderBottomWidth={index < searchResults.length - 1 ? "1px" : "0"}
borderColor={borderColor}
>
<Flex align="center" justify="space-between">
<VStack align="start" spacing={1} flex={1}>
<Text fontWeight="bold" color={textColor}>{stock.stock_name}</Text>
<HStack spacing={2}>
<Text fontSize="sm" color={subTextColor}>{stock.stock_code}</Text>
{stock.pinyin_abbr && (
<Text fontSize="xs" color={subTextColor}>({stock.pinyin_abbr.toUpperCase()})</Text>
)}
{stock.exchange && (
<Tag
size="sm"
bg={colorMode === 'dark' ? '#2a2a2a' : 'blue.50'}
color={colorMode === 'dark' ? goldColor : 'blue.600'}
border="1px solid"
borderColor={colorMode === 'dark' ? goldColor : 'blue.200'}
>
{stock.exchange}
</Tag>
)}
</HStack>
</VStack>
<Button
size="sm"
rightIcon={<ArrowRight size={16} />}
variant="ghost"
colorScheme={colorMode === 'dark' ? 'yellow' : 'purple'}
_hover={{
bg: colorMode === 'dark' ? 'whiteAlpha.100' : 'purple.50'
}}
>
查看
</Button>
</Flex>
</ListItem>
))}
</List>
) : (
<Center p={4}>
<Text color={subTextColor}>未找到相关股票</Text>
</Center>
)}
</Box>
</Collapse>
</Box>
{/* 统计数据 - 使用市场统计API数据 */}
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={6} w="100%" maxW="4xl">
<Stat
textAlign="center"
bg={colorMode === 'dark' ? 'whiteAlpha.100' : 'whiteAlpha.800'}
p={4}
borderRadius="lg"
border={colorMode === 'dark' ? `1px solid ${goldColor}50` : 'none'}
>
<StatLabel color={colorMode === 'dark' ? goldColor : 'purple.700'} fontWeight="bold">A股总市值</StatLabel>
<StatNumber fontSize="2xl" color={colorMode === 'dark' ? 'white' : 'purple.800'}>
{marketStats ?
`${(marketStats.total_market_cap / 10000).toFixed(1)}万亿`
: '-'
}
</StatNumber>
</Stat>
<Stat
textAlign="center"
bg={colorMode === 'dark' ? 'whiteAlpha.100' : 'whiteAlpha.800'}
p={4}
borderRadius="lg"
border={colorMode === 'dark' ? `1px solid ${goldColor}50` : 'none'}
>
<StatLabel color={colorMode === 'dark' ? goldColor : 'purple.700'} fontWeight="bold">今日成交额</StatLabel>
<StatNumber fontSize="2xl" color={colorMode === 'dark' ? 'white' : 'purple.800'}>
{marketStats ?
`${(marketStats.total_amount / 10000).toFixed(1)}万亿`
: '-'
}
</StatNumber>
</Stat>
<Stat
textAlign="center"
bg={colorMode === 'dark' ? 'whiteAlpha.100' : 'whiteAlpha.800'}
p={4}
borderRadius="lg"
border={colorMode === 'dark' ? `1px solid ${goldColor}50` : 'none'}
>
<StatLabel color={colorMode === 'dark' ? goldColor : 'purple.700'} fontWeight="bold">上涨家数</StatLabel>
<StatNumber fontSize="2xl" color={colorMode === 'dark' ? '#ff4d4d' : 'red.500'}>
{marketStats && marketStats.rising_count !== undefined && marketStats.rising_count !== null ?
marketStats.rising_count.toLocaleString() : '-'
}
</StatNumber>
</Stat>
<Stat
textAlign="center"
bg={colorMode === 'dark' ? 'whiteAlpha.100' : 'whiteAlpha.800'}
p={4}
borderRadius="lg"
border={colorMode === 'dark' ? `1px solid ${goldColor}50` : 'none'}
>
<StatLabel color={colorMode === 'dark' ? goldColor : 'purple.700'} fontWeight="bold">下跌家数</StatLabel>
<StatNumber fontSize="2xl" color="green.400">
{marketStats && marketStats.falling_count !== undefined && marketStats.falling_count !== null ?
marketStats.falling_count.toLocaleString() : '-'
}
</StatNumber>
</Stat>
</SimpleGrid>
</VStack>
</Box>
</Box>
{/* Hero Section - 使用通用 HeroSection 组件 */}
<HeroSection
icon={TrendingUp}
title="个股中心"
subtitle="实时追踪市场动态,洞察投资机会"
themePreset="purple"
themeColors={{
titleGradient: `linear(to-r, ${goldColor}, white)`,
iconColor: goldColor,
statLabelColor: goldColor,
statCardBorder: `${goldColor}50`,
}}
decorations={[
{
type: 'glowOrbs',
intensity: 0.4,
orbs: [
{ top: '-20%', right: '-10%', size: '40%', color: `${goldColor}15`, blur: '60px' },
],
},
]}
search={{
placeholder: '搜索股票代码、名称或拼音首字母...',
showSearchButton: true,
searchButtonText: '搜索',
maxWidth: '2xl',
// 受控模式
value: searchQuery,
onChange: (value) => {
setSearchQuery(value);
if (value && !searchQuery) {
trackSearchInitiated();
}
debounceSearch(value);
},
onClear: handleClearSearch,
results: searchResults.map((stock) => ({
id: stock.stock_code,
label: stock.stock_name,
subLabel: stock.stock_code,
extra: stock.pinyin_abbr?.toUpperCase(),
tags: stock.exchange ? [{ text: stock.exchange }] : [],
raw: stock,
})),
isSearching: isSearching,
showDropdown: showResults,
onSearch: async () => [],
onResultSelect: (item, index) => handleSelectStock(item.raw, index),
}}
stats={{
columns: { base: 2, md: 4 },
items: [
{
key: 'marketCap',
label: 'A股总市值',
value: marketStats ? `${(marketStats.total_market_cap / 10000).toFixed(1)}万亿` : null,
},
{
key: 'amount',
label: '今日成交额',
value: marketStats ? `${(marketStats.total_amount / 10000).toFixed(1)}万亿` : null,
},
{
key: 'rising',
label: '上涨家数',
value: marketStats?.rising_count,
valueColor: '#ff4d4d',
},
{
key: 'falling',
label: '下跌家数',
value: marketStats?.falling_count,
valueColor: 'green.400',
},
],
}}
/>
{/* 主内容区 */}
<Box py={10} px={6} position="relative" zIndex={1}>