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