refactor(TradeDatePicker): 组件重构,配置提取与性能优化

- 拆分文件:types.ts(类型)、theme.ts(主题)、utils.ts(工具函数)
- 移除 isDarkMode 相关代码(已确认仅浅色模式)
- 移除 useColorModeValue,直接使用固定颜色值
- 子组件使用 memo 优化,主组件使用 useCallback/useMemo
- 清理冗余:移除未使用的 tipIcon、重复的 focus 样式
- 更新调用方移除 isDarkMode prop

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-31 13:23:28 +08:00
parent 70fdad9751
commit 927668bb9c
6 changed files with 180 additions and 129 deletions

View File

@@ -1,45 +1,55 @@
import React from 'react'; /**
import { * TradeDatePicker 交易日期选择器组件
HStack, */
Input,
Text, import React, { memo, useCallback, useMemo, useEffect } from 'react';
Icon, import { HStack, Input, Text, Icon, Tooltip } from '@chakra-ui/react';
Tooltip,
useColorModeValue,
} from '@chakra-ui/react';
import { Info, Calendar } from 'lucide-react'; import { Info, Calendar } from 'lucide-react';
export interface TradeDatePickerProps { import type { TradeDatePickerProps } from './types';
/** 当前选中的日期 */ import { COLORS, INPUT_HOVER_STYLE, SIZE_CONFIG } from './theme';
value: Date | null; import { formatDateToString, formatDateToChinese, getTodayString } from './utils';
/** 日期变化回调 */
onChange: (date: Date) => void;
/** 默认日期(组件初始化时使用) */
defaultDate?: Date;
/** 最新交易日期(用于显示提示) */
latestTradeDate?: Date | null;
/** 最小可选日期 */
minDate?: Date;
/** 最大可选日期,默认今天 */
maxDate?: Date;
/** 标签文字,默认"交易日期" */
label?: string;
/** 输入框宽度 */
inputWidth?: string | object;
/** 是否显示标签图标 */
showIcon?: boolean;
/** 是否使用深色模式(强制覆盖 Chakra 颜色模式) */
isDarkMode?: boolean;
/** 是否显示最新交易日期提示,默认 true */
showLatestTradeDateTip?: boolean;
}
/** export type { TradeDatePickerProps } from './types';
* 交易日期选择器组件
* /** 日期标签 */
* 提供日期输入框和最新交易日期提示,供概念中心、个股中心等页面复用。 const DateLabel = memo<{
* 快捷按钮(今天、昨天等)由各页面自行实现。 label: string;
*/ showIcon: boolean;
iconSize: number;
spacing: number;
}>(({ label, showIcon, iconSize, spacing }) => (
<HStack spacing={spacing}>
{showIcon && <Icon as={Calendar} color={COLORS.icon} boxSize={iconSize} />}
<Text fontWeight="bold" color={COLORS.label}>
{label}
</Text>
</HStack>
));
DateLabel.displayName = 'DateLabel';
/** 最新交易日期提示 */
const LatestDateTip = memo<{ date: Date }>(({ date }) => (
<Tooltip label="数据库中最新的交易日期">
<HStack
spacing={1.5}
ml="auto"
px={2}
py={1}
opacity={0.7}
_hover={{ opacity: 1 }}
transition="opacity 0.2s"
>
<Info size={12} color="var(--chakra-colors-blue-300)" style={{ display: 'inline-block' }} />
<Text fontSize="xs" color={COLORS.tipText}>
{formatDateToChinese(date)}
</Text>
</HStack>
</Tooltip>
));
LatestDateTip.displayName = 'LatestDateTip';
/** 主组件 */
const TradeDatePicker: React.FC<TradeDatePickerProps> = ({ const TradeDatePicker: React.FC<TradeDatePickerProps> = ({
value, value,
onChange, onChange,
@@ -50,121 +60,69 @@ const TradeDatePicker: React.FC<TradeDatePickerProps> = ({
label = '交易日期', label = '交易日期',
inputWidth = { base: '100%', lg: '200px' }, inputWidth = { base: '100%', lg: '200px' },
showIcon = true, showIcon = true,
isDarkMode = false,
showLatestTradeDateTip = true, showLatestTradeDateTip = true,
size = 'md',
}) => { }) => {
// 颜色主题 - 支持 isDarkMode 强制覆盖 const sizeConfig = SIZE_CONFIG[size];
const defaultLabelColor = useColorModeValue('purple.700', 'purple.300');
const defaultIconColor = useColorModeValue('purple.500', 'purple.400');
const defaultInputBorderColor = useColorModeValue('purple.200', 'purple.600');
const defaultTipBg = useColorModeValue('blue.50', 'blue.900');
const defaultTipBorderColor = useColorModeValue('blue.200', 'blue.600');
const defaultTipTextColor = useColorModeValue('blue.600', 'blue.200');
const defaultTipIconColor = useColorModeValue('blue.500', 'blue.300');
// 深色模式专用颜色 // 默认日期初始化
const darkModeColors = { useEffect(() => {
labelColor: 'white',
iconColor: 'cyan.400',
inputBorderColor: 'whiteAlpha.300',
inputBg: 'whiteAlpha.50',
inputColor: 'white',
tipBg: 'rgba(59, 130, 246, 0.15)',
tipBorderColor: 'blue.500',
tipTextColor: 'blue.200',
tipIconColor: 'blue.300',
};
// 根据 isDarkMode 选择颜色
const labelColor = isDarkMode ? darkModeColors.labelColor : defaultLabelColor;
const iconColor = isDarkMode ? darkModeColors.iconColor : defaultIconColor;
const inputBorderColor = isDarkMode ? darkModeColors.inputBorderColor : defaultInputBorderColor;
const tipBg = isDarkMode ? darkModeColors.tipBg : defaultTipBg;
const tipBorderColor = isDarkMode ? darkModeColors.tipBorderColor : defaultTipBorderColor;
const tipTextColor = isDarkMode ? darkModeColors.tipTextColor : defaultTipTextColor;
const tipIconColor = isDarkMode ? darkModeColors.tipIconColor : defaultTipIconColor;
// 使用默认日期初始化(仅在 value 为 null 且有 defaultDate 时)
React.useEffect(() => {
if (value === null && defaultDate) { if (value === null && defaultDate) {
onChange(defaultDate); onChange(defaultDate);
} }
}, []); // eslint-disable-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// 处理日期变化 const handleDateChange = useCallback(
const handleDateChange = (e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
const dateStr = e.target.value; if (e.target.value) {
if (dateStr) { onChange(new Date(e.target.value));
const date = new Date(dateStr); }
onChange(date); },
} [onChange]
}; );
// 格式化日期为 YYYY-MM-DD const { minDateStr, maxDateStr } = useMemo(
const formatDateValue = (date: Date | null): string => { () => ({
if (!date) return ''; minDateStr: minDate ? formatDateToString(minDate) : undefined,
return date.toISOString().split('T')[0]; maxDateStr: maxDate ? formatDateToString(maxDate) : getTodayString(),
}; }),
[minDate, maxDate]
);
// 计算日期范围 const valueStr = useMemo(() => formatDateToString(value), [value]);
const minDateStr = minDate ? formatDateValue(minDate) : undefined;
const maxDateStr = maxDate
? formatDateValue(maxDate)
: new Date().toISOString().split('T')[0];
return ( return (
<> <>
{/* 标签 */} <DateLabel
<HStack spacing={3}> label={label}
{showIcon && <Icon as={Calendar} color={iconColor} boxSize={5} />} showIcon={showIcon}
<Text fontWeight="bold" color={labelColor}> iconSize={sizeConfig.iconSize}
{label} spacing={sizeConfig.spacing}
</Text> />
</HStack>
{/* 日期输入框 */}
<Input <Input
type="date" type="date"
value={formatDateValue(value)} value={valueStr}
onChange={handleDateChange} onChange={handleDateChange}
min={minDateStr} min={minDateStr}
max={maxDateStr} max={maxDateStr}
width={inputWidth} width={inputWidth}
height={sizeConfig.inputHeight}
fontSize={sizeConfig.fontSize}
focusBorderColor="purple.400" focusBorderColor="purple.400"
borderColor={inputBorderColor} borderColor={COLORS.inputBorder}
borderRadius="lg" borderRadius="lg"
fontWeight="medium" fontWeight="medium"
bg={isDarkMode ? darkModeColors.inputBg : undefined} transition="all 0.2s"
color={isDarkMode ? darkModeColors.inputColor : undefined} _hover={INPUT_HOVER_STYLE}
_hover={{ borderColor: isDarkMode ? 'purple.400' : 'purple.300' }}
sx={isDarkMode ? {
'&::-webkit-calendar-picker-indicator': {
filter: 'invert(1)',
},
} : undefined}
/> />
{/* 最新交易日期提示 - 靠右显示,样式更低调避免误认为按钮 */}
{showLatestTradeDateTip && latestTradeDate && ( {showLatestTradeDateTip && latestTradeDate && (
<Tooltip label="数据库中最新的交易日期"> <LatestDateTip date={latestTradeDate} />
<HStack
spacing={1.5}
ml="auto"
px={2}
py={1}
opacity={0.7}
_hover={{ opacity: 1 }}
transition="opacity 0.2s"
>
<Info size={12} color="var(--chakra-colors-blue-300)" style={{ display: 'inline-block' }} />
<Text fontSize="xs" color={tipTextColor}>
{latestTradeDate.toLocaleDateString('zh-CN')}
</Text>
</HStack>
</Tooltip>
)} )}
</> </>
); );
}; };
export default TradeDatePicker; export default memo(TradeDatePicker);

View File

@@ -0,0 +1,30 @@
/**
* TradeDatePicker 主题配置
*/
/** 颜色配置 */
export const COLORS = {
label: 'purple.700',
icon: 'purple.500',
inputBorder: 'purple.200',
tipText: 'blue.600',
};
/** 输入框悬浮样式 */
export const INPUT_HOVER_STYLE = { borderColor: 'purple.300' };
/** 尺寸配置 */
export const SIZE_CONFIG = {
sm: {
inputHeight: '32px',
fontSize: 'sm',
iconSize: 4,
spacing: 2,
},
md: {
inputHeight: '40px',
fontSize: 'md',
iconSize: 5,
spacing: 3,
},
};

View File

@@ -0,0 +1,31 @@
/**
* TradeDatePicker 类型定义
*/
import type { ResponsiveValue } from '@chakra-ui/react';
/** 组件 Props */
export interface TradeDatePickerProps {
/** 当前选中的日期 */
value: Date | null;
/** 日期变化回调 */
onChange: (date: Date) => void;
/** 默认日期(组件初始化时使用) */
defaultDate?: Date;
/** 最新交易日期(用于显示提示) */
latestTradeDate?: Date | null;
/** 最小可选日期 */
minDate?: Date;
/** 最大可选日期,默认今天 */
maxDate?: Date;
/** 标签文字,默认"交易日期" */
label?: string;
/** 输入框宽度 */
inputWidth?: ResponsiveValue<string>;
/** 是否显示标签图标 */
showIcon?: boolean;
/** 是否显示最新交易日期提示,默认 true */
showLatestTradeDateTip?: boolean;
/** 尺寸sm | md */
size?: 'sm' | 'md';
}

View File

@@ -0,0 +1,34 @@
/**
* TradeDatePicker 工具函数
*/
/**
* 格式化日期为 YYYY-MM-DD 字符串
* @param date - 日期对象
* @returns 格式化后的日期字符串,如果 date 为 null 则返回空字符串
*/
export const formatDateToString = (date: Date | null): string => {
if (!date) return '';
// 使用本地时间避免时区问题
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
/**
* 格式化日期为中文本地化字符串
* @param date - 日期对象
* @returns 中文格式的日期字符串
*/
export const formatDateToChinese = (date: Date): string => {
return date.toLocaleDateString('zh-CN');
};
/**
* 获取今天的日期字符串 (YYYY-MM-DD)
* @returns 今天的日期字符串
*/
export const getTodayString = (): string => {
return formatDateToString(new Date());
};

View File

@@ -1467,7 +1467,6 @@ const ConceptCenter = () => {
}} }}
latestTradeDate={latestTradeDate} latestTradeDate={latestTradeDate}
label="交易日期" label="交易日期"
isDarkMode={true}
showLatestTradeDateTip={false} showLatestTradeDateTip={false}
/> />

View File

@@ -440,7 +440,6 @@ const HotspotOverview = ({ selectedDate, onDateChange, minDate, maxDate }) => {
latestTradeDate={null} latestTradeDate={null}
minDate={minDate} minDate={minDate}
maxDate={maxDate} maxDate={maxDate}
isDarkMode={true}
size="sm" size="sm"
/> />
)} )}