feat: 添加交易时间段筛选组件
This commit is contained in:
340
src/views/Community/components/TradingTimeFilter.js
Normal file
340
src/views/Community/components/TradingTimeFilter.js
Normal 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;
|
||||||
Reference in New Issue
Block a user