feat: 添加交易时间段筛选组件

This commit is contained in:
zdl
2025-11-06 11:46:31 +08:00
parent 5ff8db8899
commit f4b58b42cc

View File

@@ -0,0 +1,340 @@
// src/views/Community/components/TradingTimeFilter.js
// 交易时段智能筛选组件
import React, { useState, useMemo } from 'react';
import { Space, Button, Tag, Tooltip, DatePicker, Popover } from 'antd';
import { ClockCircleOutlined, CalendarOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import locale from 'antd/es/date-picker/locale/zh_CN';
import { logger } from '../../../utils/logger';
const { RangePicker } = DatePicker;
/**
* 交易时段筛选组件
* @param {Function} onChange - 时间范围变化回调 (timeConfig) => void
*/
const TradingTimeFilter = ({ onChange }) => {
const [selectedKey, setSelectedKey] = useState(null);
const [customRangeVisible, setCustomRangeVisible] = useState(false);
const [customRange, setCustomRange] = useState(null);
// 获取当前交易时段
const getCurrentTradingSession = () => {
const now = dayjs();
const hour = now.hour();
const minute = now.minute();
const currentMinutes = hour * 60 + minute;
// 转换为分钟数便于比较
const PRE_MARKET_END = 9 * 60 + 30; // 09:30
const MORNING_END = 11 * 60 + 30; // 11:30
const AFTERNOON_START = 13 * 60; // 13:00
const MARKET_CLOSE = 15 * 60; // 15:00
if (currentMinutes < PRE_MARKET_END) {
return 'pre-market'; // 盘前
} else if (currentMinutes < MORNING_END) {
return 'morning'; // 早盘
} else if (currentMinutes < AFTERNOON_START) {
return 'lunch'; // 午休
} else if (currentMinutes < MARKET_CLOSE) {
return 'afternoon'; // 午盘
} else {
return 'after-hours'; // 盘后
}
};
// 获取时间范围配置
const timeRangeConfig = useMemo(() => {
const session = getCurrentTradingSession();
const now = dayjs();
// 今日关键时间点
const today0930 = now.hour(9).minute(30).second(0);
const today1130 = now.hour(11).minute(30).second(0);
const today1300 = now.hour(13).minute(0).second(0);
const today1500 = now.hour(15).minute(0).second(0);
const todayStart = now.startOf('day');
const todayEnd = now.endOf('day');
// 昨日关键时间点
const yesterday1500 = now.subtract(1, 'day').hour(15).minute(0).second(0);
const yesterdayStart = now.subtract(1, 'day').startOf('day');
const yesterdayEnd = now.subtract(1, 'day').endOf('day');
// 动态按钮配置(根据时段返回不同按钮数组)
const dynamicButtonsMap = {
'pre-market': [
{
key: 'latest',
label: '最新',
range: [yesterday1500, today0930],
tooltip: '盘前资讯',
timeHint: `昨日 15:00 - 今日 09:30`,
color: 'purple',
type: 'precise'
}
],
'morning': [
{
key: 'latest',
label: '最新',
range: [today0930, now],
tooltip: '早盘最新',
timeHint: `今日 09:30 - ${now.format('HH:mm')}`,
color: 'green',
type: 'precise'
},
{
key: 'intraday',
label: '盘中',
range: [today0930, today1500],
tooltip: '盘中交易时段',
timeHint: '今日 09:30 - 15:00',
color: 'blue',
type: 'precise'
}
],
'lunch': [
{
key: 'latest',
label: '最新',
range: [today1130, now],
tooltip: '午休时段',
timeHint: `今日 11:30 - ${now.format('HH:mm')}`,
color: 'orange',
type: 'precise'
}
],
'afternoon': [
{
key: 'latest',
label: '最新',
range: [today1300, now],
tooltip: '午盘最新',
timeHint: `今日 13:00 - ${now.format('HH:mm')}`,
color: 'green',
type: 'precise'
},
{
key: 'intraday',
label: '盘中',
range: [today0930, today1500],
tooltip: '盘中交易时段',
timeHint: '今日 09:30 - 15:00',
color: 'blue',
type: 'precise'
},
{
key: 'afternoon',
label: '午盘',
range: [today1300, today1500],
tooltip: '午盘交易时段',
timeHint: '今日 13:00 - 15:00',
color: 'cyan',
type: 'precise'
}
],
'after-hours': [
{
key: 'latest',
label: '最新',
range: [today1500, now],
tooltip: '盘后最新',
timeHint: `今日 15:00 - ${now.format('HH:mm')}`,
color: 'red',
type: 'precise'
}
]
};
// 固定按钮配置(始终显示)
const fixedButtons = [
{
key: 'morning-fixed',
label: '早盘',
range: [today0930, today1130],
tooltip: '早盘交易时段',
timeHint: '09:30 - 11:30',
color: 'geekblue',
type: 'precise'
},
{
key: 'today',
label: '今日全天',
range: [todayStart, todayEnd],
tooltip: '今日全天',
timeHint: '今日 00:00 - 23:59',
color: 'purple',
type: 'precise'
},
{
key: 'yesterday',
label: '昨日',
range: [yesterdayStart, yesterdayEnd],
tooltip: '昨日全天',
timeHint: '昨日 00:00 - 23:59',
color: 'orange',
type: 'precise'
},
{
key: 'week',
label: '近一周',
range: 7, // 天数
tooltip: '过去7个交易日',
timeHint: '过去7天',
color: 'magenta',
type: 'recent_days'
},
{
key: 'month',
label: '近一月',
range: 30, // 天数
tooltip: '过去30个交易日',
timeHint: '过去30天',
color: 'volcano',
type: 'recent_days'
}
];
return {
dynamic: dynamicButtonsMap[session] || [],
fixed: fixedButtons
};
}, []); // 空依赖,首次渲染时计算
// 按钮点击处理
const handleButtonClick = (config) => {
logger.debug('TradingTimeFilter', '按钮点击', { config });
if (selectedKey === config.key) {
// 取消选中
setSelectedKey(null);
onChange(null);
} else {
// 选中
setSelectedKey(config.key);
onChange({
range: config.range,
type: config.type,
label: config.label,
key: config.key
});
}
};
// 自定义范围确认
const handleCustomRangeOk = (dates) => {
if (dates && dates.length === 2) {
setCustomRange(dates);
setSelectedKey('custom');
setCustomRangeVisible(false);
onChange({
range: dates,
type: 'precise',
label: `${dates[0].format('MM-DD HH:mm')} - ${dates[1].format('MM-DD HH:mm')}`,
key: 'custom'
});
logger.debug('TradingTimeFilter', '自定义范围', {
start: dates[0].format('YYYY-MM-DD HH:mm:ss'),
end: dates[1].format('YYYY-MM-DD HH:mm:ss')
});
}
};
// 渲染按钮
const renderButton = (config) => {
const isSelected = selectedKey === config.key;
// 构建完整的 tooltip 提示(文字 + 时间)
const fullTooltip = config.timeHint ? `${config.tooltip} · ${config.timeHint}` : config.tooltip;
if (isSelected) {
// 选中状态:只显示 Tag不显示下方时间
return (
<Tooltip title={fullTooltip} key={config.key}>
<Tag
color={config.color}
style={{ margin: 0, fontSize: 13, padding: '2px 8px', cursor: 'pointer' }}
onClick={() => handleButtonClick(config)}
>
<ClockCircleOutlined style={{ marginRight: 4 }} />
{config.label}
</Tag>
</Tooltip>
);
} else {
// 未选中状态:普通按钮
return (
<Tooltip title={fullTooltip} key={config.key}>
<Button
size="small"
onClick={() => handleButtonClick(config)}
style={{ fontSize: 12 }}
>
{config.label}
</Button>
</Tooltip>
);
}
};
// "更多时间" 按钮内容
const customRangeContent = (
<div style={{ padding: 8 }}>
<RangePicker
showTime={{ format: 'HH:mm' }}
format="YYYY-MM-DD HH:mm"
locale={locale}
placeholder={['开始时间', '结束时间']}
onChange={handleCustomRangeOk}
value={customRange}
style={{ marginBottom: 8 }}
/>
<div style={{ fontSize: 12, color: '#999', marginTop: 4 }}>
支持精确到分钟的时间范围选择
</div>
</div>
);
return (
<Space wrap size={[8, 8]} style={{ display: 'flex', alignItems: 'flex-start' }}>
{/* 动态按钮(根据时段显示多个) */}
{timeRangeConfig.dynamic.map(config => renderButton(config))}
{/* 固定按钮(始终显示) */}
{timeRangeConfig.fixed.map(config => renderButton(config))}
{/* 更多时间 - 自定义范围 */}
<Popover
content={customRangeContent}
title="选择自定义时间范围"
trigger="click"
open={customRangeVisible}
onOpenChange={setCustomRangeVisible}
placement="bottomLeft"
>
{selectedKey === 'custom' ? (
<Tooltip
title={customRange ? `自定义时间范围 · ${customRange[0].format('MM-DD HH:mm')} - ${customRange[1].format('MM-DD HH:mm')}` : '自定义时间范围'}
>
<Tag color="gold" style={{ margin: 0, fontSize: 13, padding: '2px 8px', cursor: 'pointer' }}>
<CalendarOutlined style={{ marginRight: 4 }} />
自定义
</Tag>
</Tooltip>
) : (
<Tooltip title="自定义时间范围">
<Button size="small" icon={<CalendarOutlined />} style={{ fontSize: 12 }}>
更多时间
</Button>
</Tooltip>
)}
</Popover>
</Space>
);
};
export default TradingTimeFilter;