feat: 个股中心复用 TradeDatePicker 日期选择器组件

- StockOverview: 替换 Popover 日期选择器为 TradeDatePicker
- StockOverview: 修复 selectedDate 类型从字符串改为 Date 对象
- StockOverview: 隐藏"最新交易日期"提示
- TradeDatePicker: 新增 minDate 属性支持日期范围限制
- 日期选择器可选范围限制为 tradingDays 数据范围

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-04 16:47:44 +08:00
parent b5d054d89f
commit 4a97f87ee5
2 changed files with 29 additions and 95 deletions

View File

@@ -19,6 +19,8 @@ export interface TradeDatePickerProps {
defaultDate?: Date;
/** 最新交易日期(用于显示提示) */
latestTradeDate?: Date | null;
/** 最小可选日期 */
minDate?: Date;
/** 最大可选日期,默认今天 */
maxDate?: Date;
/** 标签文字,默认"交易日期" */
@@ -40,6 +42,7 @@ const TradeDatePicker: React.FC<TradeDatePickerProps> = ({
onChange,
defaultDate,
latestTradeDate,
minDate,
maxDate,
label = '交易日期',
inputWidth = { base: '100%', lg: '200px' },
@@ -76,7 +79,8 @@ const TradeDatePicker: React.FC<TradeDatePickerProps> = ({
return date.toISOString().split('T')[0];
};
// 计算最大日期
// 计算日期范围
const minDateStr = minDate ? formatDateValue(minDate) : undefined;
const maxDateStr = maxDate
? formatDateValue(maxDate)
: new Date().toISOString().split('T')[0];
@@ -96,6 +100,7 @@ const TradeDatePicker: React.FC<TradeDatePickerProps> = ({
type="date"
value={formatDateValue(value)}
onChange={handleDateChange}
min={minDateStr}
max={maxDateStr}
width={inputWidth}
focusBorderColor="purple.500"

View File

@@ -27,7 +27,6 @@ import {
Spacer,
Icon,
useColorModeValue,
useColorMode,
useToast,
Spinner,
Center,
@@ -49,14 +48,11 @@ import {
TagLabel,
Skeleton,
SkeletonText,
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
} from '@chakra-ui/react';
import { SearchIcon, CloseIcon, ArrowForwardIcon, TrendingUpIcon, InfoIcon, ChevronRightIcon, MoonIcon, SunIcon, CalendarIcon } from '@chakra-ui/icons';
import { SearchIcon, CloseIcon, ArrowForwardIcon, TrendingUpIcon, InfoIcon, ChevronRightIcon, CalendarIcon } from '@chakra-ui/icons';
import { FaChartLine, FaFire, FaRocket, FaBrain, FaCalendarAlt, FaChevronRight, FaArrowUp, FaArrowDown, FaChartBar } from 'react-icons/fa';
import ConceptStocksModal from '@components/ConceptStocksModal';
import TradeDatePicker from '@components/TradeDatePicker';
import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
import * as echarts from 'echarts';
import { logger } from '../../utils/logger';
@@ -71,7 +67,7 @@ const tradingDaysSet = new Set(tradingDays);
const StockOverview = () => {
const navigate = useNavigate();
const toast = useToast();
const { colorMode, toggleColorMode } = useColorMode();
const colorMode = 'light'; // 固定为 light 模式
const heatmapRef = useRef(null);
const heatmapChart = useRef(null);
@@ -101,7 +97,6 @@ const StockOverview = () => {
const [selectedDate, setSelectedDate] = useState(null);
const [marketStats, setMarketStats] = useState(null);
const [availableDates, setAvailableDates] = useState([]);
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
// 个股列表弹窗状态
const [isStockModalOpen, setIsStockModalOpen] = useState(false);
@@ -190,7 +185,7 @@ const StockOverview = () => {
if (data.success) {
setTopConcepts(data.data);
// 使用概念接口的日期作为统一数据源(数据最新)
setSelectedDate(data.trade_date);
setSelectedDate(new Date(data.trade_date));
// 基于交易日历生成可选日期列表
if (data.trade_date && tradingDays.length > 0) {
// 找到当前日期或最近的交易日
@@ -518,20 +513,6 @@ const StockOverview = () => {
window.open(htmlPath, '_blank');
};
// 处理日期选择
const handleDateChange = (date) => {
const previousDate = selectedDate;
// 🎯 追踪日期变化
trackDateChanged(date, previousDate);
setSelectedDate(date);
setIsCalendarOpen(false);
// 重新获取数据
fetchHeatmapData(date);
fetchMarketStats(date);
fetchTopConcepts(date);
};
// 格式化涨跌幅
const formatChangePercent = (value) => {
@@ -620,25 +601,6 @@ const StockOverview = () => {
filter="blur(40px)"
/>
{/* 日夜模式切换按钮 */}
<Box position="absolute" top={4} right={4}>
<IconButton
aria-label="Toggle color mode"
icon={colorMode === 'light' ? <MoonIcon /> : <SunIcon />}
onClick={toggleColorMode}
size="lg"
bg={colorMode === 'dark' ? '#1a1a1a' : 'white'}
color={colorMode === 'dark' ? goldColor : 'purple.600'}
border="2px solid"
borderColor={colorMode === 'dark' ? goldColor : 'purple.200'}
_hover={{
bg: colorMode === 'dark' ? '#2a2a2a' : 'purple.50',
transform: 'scale(1.1)'
}}
transition="all 0.3s"
/>
</Box>
<Container maxW="container.xl" position="relative">
<VStack spacing={8} align="center">
<VStack spacing={4} textAlign="center" maxW="3xl">
@@ -853,60 +815,27 @@ const StockOverview = () => {
<Container maxW="container.xl" py={10}>
{/* 日期选择器 */}
<Box mb={6}>
<Popover isOpen={isCalendarOpen} onClose={() => setIsCalendarOpen(false)}>
<PopoverTrigger>
<Button
leftIcon={<CalendarIcon />}
onClick={() => setIsCalendarOpen(!isCalendarOpen)}
variant="outline"
size="md"
colorScheme={colorMode === 'dark' ? 'yellow' : 'purple'}
bg={colorMode === 'dark' ? '#1a1a1a' : 'white'}
border="2px solid"
borderColor={colorMode === 'dark' ? goldColor : 'purple.300'}
_hover={{
bg: colorMode === 'dark' ? '#2a2a2a' : 'purple.50'
<Flex align="center" gap={4} flexWrap="wrap">
<TradeDatePicker
value={selectedDate}
onChange={(date) => {
const dateStr = date.toISOString().split('T')[0];
const previousDateStr = selectedDate ? selectedDate.toISOString().split('T')[0] : null;
trackDateChanged(dateStr, previousDateStr);
setSelectedDate(date);
fetchHeatmapData(dateStr);
fetchMarketStats(dateStr);
fetchTopConcepts(dateStr);
}}
>
{selectedDate ?
`交易日期: ${selectedDate}` :
'选择交易日期'
}
</Button>
</PopoverTrigger>
<PopoverContent bg={cardBg} borderColor={borderColor} boxShadow="xl">
<PopoverBody p={4}>
<VStack align="start" spacing={3}>
<Text fontWeight="bold" color={textColor}>选择交易日期</Text>
<Divider />
{availableDates.length > 0 ? (
<VStack align="stretch" maxH="300px" overflowY="auto" spacing={1} w="100%">
{availableDates.map((date) => (
<Button
key={date}
size="sm"
variant={selectedDate === date ? 'solid' : 'ghost'}
colorScheme={selectedDate === date ? (colorMode === 'dark' ? 'yellow' : 'purple') : 'gray'}
onClick={() => handleDateChange(date)}
justifyContent="start"
w="100%"
>
{date}
</Button>
))}
</VStack>
) : (
<Text fontSize="sm" color={subTextColor}>
暂无可用日期
</Text>
)}
</VStack>
</PopoverBody>
</PopoverContent>
</Popover>
latestTradeDate={null}
minDate={tradingDays.length > 0 ? new Date(tradingDays[0]) : undefined}
maxDate={tradingDays.length > 0 ? new Date(tradingDays[tradingDays.length - 1]) : undefined}
label="交易日期"
/>
</Flex>
{selectedDate && (
<Text fontSize="sm" color={subTextColor} mt={2}>
当前显示 {selectedDate} 的市场数据
当前显示 {selectedDate.toISOString().split('T')[0]} 的市场数据
</Text>
)}
</Box>