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:
@@ -19,6 +19,8 @@ export interface TradeDatePickerProps {
|
|||||||
defaultDate?: Date;
|
defaultDate?: Date;
|
||||||
/** 最新交易日期(用于显示提示) */
|
/** 最新交易日期(用于显示提示) */
|
||||||
latestTradeDate?: Date | null;
|
latestTradeDate?: Date | null;
|
||||||
|
/** 最小可选日期 */
|
||||||
|
minDate?: Date;
|
||||||
/** 最大可选日期,默认今天 */
|
/** 最大可选日期,默认今天 */
|
||||||
maxDate?: Date;
|
maxDate?: Date;
|
||||||
/** 标签文字,默认"交易日期" */
|
/** 标签文字,默认"交易日期" */
|
||||||
@@ -40,6 +42,7 @@ const TradeDatePicker: React.FC<TradeDatePickerProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
defaultDate,
|
defaultDate,
|
||||||
latestTradeDate,
|
latestTradeDate,
|
||||||
|
minDate,
|
||||||
maxDate,
|
maxDate,
|
||||||
label = '交易日期',
|
label = '交易日期',
|
||||||
inputWidth = { base: '100%', lg: '200px' },
|
inputWidth = { base: '100%', lg: '200px' },
|
||||||
@@ -76,7 +79,8 @@ const TradeDatePicker: React.FC<TradeDatePickerProps> = ({
|
|||||||
return date.toISOString().split('T')[0];
|
return date.toISOString().split('T')[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算最大日期
|
// 计算日期范围
|
||||||
|
const minDateStr = minDate ? formatDateValue(minDate) : undefined;
|
||||||
const maxDateStr = maxDate
|
const maxDateStr = maxDate
|
||||||
? formatDateValue(maxDate)
|
? formatDateValue(maxDate)
|
||||||
: new Date().toISOString().split('T')[0];
|
: new Date().toISOString().split('T')[0];
|
||||||
@@ -96,6 +100,7 @@ const TradeDatePicker: React.FC<TradeDatePickerProps> = ({
|
|||||||
type="date"
|
type="date"
|
||||||
value={formatDateValue(value)}
|
value={formatDateValue(value)}
|
||||||
onChange={handleDateChange}
|
onChange={handleDateChange}
|
||||||
|
min={minDateStr}
|
||||||
max={maxDateStr}
|
max={maxDateStr}
|
||||||
width={inputWidth}
|
width={inputWidth}
|
||||||
focusBorderColor="purple.500"
|
focusBorderColor="purple.500"
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import {
|
|||||||
Spacer,
|
Spacer,
|
||||||
Icon,
|
Icon,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
useColorMode,
|
|
||||||
useToast,
|
useToast,
|
||||||
Spinner,
|
Spinner,
|
||||||
Center,
|
Center,
|
||||||
@@ -49,14 +48,11 @@ import {
|
|||||||
TagLabel,
|
TagLabel,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
SkeletonText,
|
SkeletonText,
|
||||||
Popover,
|
|
||||||
PopoverTrigger,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverBody,
|
|
||||||
} from '@chakra-ui/react';
|
} 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 { FaChartLine, FaFire, FaRocket, FaBrain, FaCalendarAlt, FaChevronRight, FaArrowUp, FaArrowDown, FaChartBar } from 'react-icons/fa';
|
||||||
import ConceptStocksModal from '@components/ConceptStocksModal';
|
import ConceptStocksModal from '@components/ConceptStocksModal';
|
||||||
|
import TradeDatePicker from '@components/TradeDatePicker';
|
||||||
import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
|
import { BsGraphUp, BsLightningFill } from 'react-icons/bs';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
@@ -71,7 +67,7 @@ const tradingDaysSet = new Set(tradingDays);
|
|||||||
const StockOverview = () => {
|
const StockOverview = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { colorMode, toggleColorMode } = useColorMode();
|
const colorMode = 'light'; // 固定为 light 模式
|
||||||
const heatmapRef = useRef(null);
|
const heatmapRef = useRef(null);
|
||||||
const heatmapChart = useRef(null);
|
const heatmapChart = useRef(null);
|
||||||
|
|
||||||
@@ -101,7 +97,6 @@ const StockOverview = () => {
|
|||||||
const [selectedDate, setSelectedDate] = useState(null);
|
const [selectedDate, setSelectedDate] = useState(null);
|
||||||
const [marketStats, setMarketStats] = useState(null);
|
const [marketStats, setMarketStats] = useState(null);
|
||||||
const [availableDates, setAvailableDates] = useState([]);
|
const [availableDates, setAvailableDates] = useState([]);
|
||||||
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
|
|
||||||
|
|
||||||
// 个股列表弹窗状态
|
// 个股列表弹窗状态
|
||||||
const [isStockModalOpen, setIsStockModalOpen] = useState(false);
|
const [isStockModalOpen, setIsStockModalOpen] = useState(false);
|
||||||
@@ -190,7 +185,7 @@ const StockOverview = () => {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
setTopConcepts(data.data);
|
setTopConcepts(data.data);
|
||||||
// 使用概念接口的日期作为统一数据源(数据最新)
|
// 使用概念接口的日期作为统一数据源(数据最新)
|
||||||
setSelectedDate(data.trade_date);
|
setSelectedDate(new Date(data.trade_date));
|
||||||
// 基于交易日历生成可选日期列表
|
// 基于交易日历生成可选日期列表
|
||||||
if (data.trade_date && tradingDays.length > 0) {
|
if (data.trade_date && tradingDays.length > 0) {
|
||||||
// 找到当前日期或最近的交易日
|
// 找到当前日期或最近的交易日
|
||||||
@@ -518,20 +513,6 @@ const StockOverview = () => {
|
|||||||
window.open(htmlPath, '_blank');
|
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) => {
|
const formatChangePercent = (value) => {
|
||||||
@@ -620,25 +601,6 @@ const StockOverview = () => {
|
|||||||
filter="blur(40px)"
|
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">
|
<Container maxW="container.xl" position="relative">
|
||||||
<VStack spacing={8} align="center">
|
<VStack spacing={8} align="center">
|
||||||
<VStack spacing={4} textAlign="center" maxW="3xl">
|
<VStack spacing={4} textAlign="center" maxW="3xl">
|
||||||
@@ -853,60 +815,27 @@ const StockOverview = () => {
|
|||||||
<Container maxW="container.xl" py={10}>
|
<Container maxW="container.xl" py={10}>
|
||||||
{/* 日期选择器 */}
|
{/* 日期选择器 */}
|
||||||
<Box mb={6}>
|
<Box mb={6}>
|
||||||
<Popover isOpen={isCalendarOpen} onClose={() => setIsCalendarOpen(false)}>
|
<Flex align="center" gap={4} flexWrap="wrap">
|
||||||
<PopoverTrigger>
|
<TradeDatePicker
|
||||||
<Button
|
value={selectedDate}
|
||||||
leftIcon={<CalendarIcon />}
|
onChange={(date) => {
|
||||||
onClick={() => setIsCalendarOpen(!isCalendarOpen)}
|
const dateStr = date.toISOString().split('T')[0];
|
||||||
variant="outline"
|
const previousDateStr = selectedDate ? selectedDate.toISOString().split('T')[0] : null;
|
||||||
size="md"
|
trackDateChanged(dateStr, previousDateStr);
|
||||||
colorScheme={colorMode === 'dark' ? 'yellow' : 'purple'}
|
setSelectedDate(date);
|
||||||
bg={colorMode === 'dark' ? '#1a1a1a' : 'white'}
|
fetchHeatmapData(dateStr);
|
||||||
border="2px solid"
|
fetchMarketStats(dateStr);
|
||||||
borderColor={colorMode === 'dark' ? goldColor : 'purple.300'}
|
fetchTopConcepts(dateStr);
|
||||||
_hover={{
|
}}
|
||||||
bg: colorMode === 'dark' ? '#2a2a2a' : 'purple.50'
|
latestTradeDate={null}
|
||||||
}}
|
minDate={tradingDays.length > 0 ? new Date(tradingDays[0]) : undefined}
|
||||||
>
|
maxDate={tradingDays.length > 0 ? new Date(tradingDays[tradingDays.length - 1]) : undefined}
|
||||||
{selectedDate ?
|
label="交易日期"
|
||||||
`交易日期: ${selectedDate}` :
|
/>
|
||||||
'选择交易日期'
|
</Flex>
|
||||||
}
|
|
||||||
</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>
|
|
||||||
{selectedDate && (
|
{selectedDate && (
|
||||||
<Text fontSize="sm" color={subTextColor} mt={2}>
|
<Text fontSize="sm" color={subTextColor} mt={2}>
|
||||||
当前显示 {selectedDate} 的市场数据
|
当前显示 {selectedDate.toISOString().split('T')[0]} 的市场数据
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user