refactor(TradingTimeFilter): 提取常量和工具函数到子模块

- constants.js: TIME_BOUNDARIES, TRADING_SESSIONS 时间边界配置
- utils.js: getCurrentTradingSession, getPrevTradingDay, generateTimeRangeConfig, disabledDate/Time
- index.js: 模块统一导出

主文件从 ~440 行精简到 ~260 行

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2026-01-15 11:45:12 +08:00
parent 8a7f624589
commit da13cf08c5
4 changed files with 395 additions and 300 deletions

View File

@@ -1,239 +1,84 @@
// src/views/Community/components/TradingTimeFilter.js // src/views/Community/components/TradingTimeFilter.js
// 交易时段智能筛选组件 // 交易时段智能筛选组件
import React, { useState, useMemo, useEffect } from 'react';
import { Space, Button, Tag, Tooltip, DatePicker, Popover, Select } from 'antd'; import React, { useState, useMemo, useEffect } from "react";
import { ClockCircleOutlined, CalendarOutlined, FilterOutlined } from '@ant-design/icons'; import {
import dayjs from 'dayjs'; Space,
import locale from 'antd/es/date-picker/locale/zh_CN'; Button,
import { logger } from '@utils/logger'; Tag,
import { PROFESSIONAL_COLORS } from '@constants/professionalTheme'; Tooltip,
import tradingDayUtils from '@utils/tradingDayUtils'; DatePicker,
Popover,
Select,
} from "antd";
import {
ClockCircleOutlined,
CalendarOutlined,
FilterOutlined,
} from "@ant-design/icons";
import locale from "antd/es/date-picker/locale/zh_CN";
import { logger } from "@utils/logger";
import { PROFESSIONAL_COLORS } from "@constants/professionalTheme";
// 模块化导入
import {
generateTimeRangeConfig,
disabledDate,
disabledTime,
} from "./TradingTimeFilter/utils";
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
const { Option } = Select; const { Option } = Select;
/** /**
* 交易时段筛选组件 * 交易时段筛选组件
* @param {string} value - 当前选中的 key受控
* @param {Function} onChange - 时间范围变化回调 (timeConfig) => void
* @param {boolean} compact - 是否使用紧凑模式PC 端搜索栏内使用)
* @param {boolean} mobile - 是否使用移动端模式(下拉选择)
*/ */
const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false }) => { const TradingTimeFilter = ({
value,
onChange,
compact = false,
mobile = false,
}) => {
const [selectedKey, setSelectedKey] = useState(null); const [selectedKey, setSelectedKey] = useState(null);
const [customRangeVisible, setCustomRangeVisible] = useState(false); const [customRangeVisible, setCustomRangeVisible] = useState(false);
const [customRange, setCustomRange] = useState(null); const [customRange, setCustomRange] = useState(null);
// 监听外部 value 变化,同步内部状态 // 监听外部 value 变化
useEffect(() => { useEffect(() => {
if (value === null || value === undefined) { if (value === null || value === undefined) {
// 外部重置,清空内部状态
setSelectedKey(null); setSelectedKey(null);
setCustomRange(null); setCustomRange(null);
logger.debug('TradingTimeFilter', '外部重置,清空选中状态'); logger.debug("TradingTimeFilter", "外部重置,清空选中状态");
} else { } else {
// 外部选中值变化,同步内部状态
setSelectedKey(value); setSelectedKey(value);
logger.debug('TradingTimeFilter', '外部value变化同步内部状态', { value }); logger.debug("TradingTimeFilter", "外部value变化同步内部状态", { value });
} }
}, [value]); }, [value]);
// 获取当前交易时段
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 timeRangeConfig = useMemo(() => generateTimeRangeConfig(), []);
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': [], // 盘前:使用"当前交易日"即可
'morning': [
{
key: 'intraday',
label: '盘中',
range: [today0930, today1500],
tooltip: '盘中交易时段',
timeHint: '今日 09:30 - 15:00',
color: 'blue',
type: 'precise'
}
],
'lunch': [], // 午休:使用"当前交易日"即可
'afternoon': [
{
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': [] // 盘后:使用"当前交易日"即可
};
// 获取上一个交易日(使用 tdays.csv 数据)
const getPrevTradingDay = () => {
try {
const prevTradingDay = tradingDayUtils.getPreviousTradingDay(now.toDate());
return dayjs(prevTradingDay);
} catch (e) {
// 降级:简单地减一天(不考虑周末节假日)
logger.warn('TradingTimeFilter', '获取上一交易日失败,降级处理', e);
return now.subtract(1, 'day');
}
};
const prevTradingDay = getPrevTradingDay();
const prevTradingDay1500 = prevTradingDay.hour(15).minute(0).second(0);
// 固定按钮配置(始终显示)
const fixedButtons = [
{
key: 'current-trading-day',
label: '当前交易日',
range: [prevTradingDay1500, now],
tooltip: '当前交易日事件',
timeHint: `${prevTradingDay.format('MM-DD')} 15:00 - 现在`,
color: 'green',
type: 'precise'
},
{
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'
},
{
key: 'all',
label: '全部',
range: null, // 无时间限制
tooltip: '显示全部事件',
timeHint: '不限时间',
color: 'default',
type: 'all'
}
];
return {
dynamic: dynamicButtonsMap[session] || [],
fixed: fixedButtons
};
}, []); // 空依赖,首次渲染时计算
// 按钮点击处理 // 按钮点击处理
const handleButtonClick = (config) => { const handleButtonClick = (config) => {
logger.debug('TradingTimeFilter', '按钮点击', { logger.debug("TradingTimeFilter", "按钮点击", {
config, config,
currentSelectedKey: selectedKey, currentSelectedKey: selectedKey,
willToggle: selectedKey === config.key willToggle: selectedKey === config.key,
}); });
if (selectedKey === config.key) { if (selectedKey === config.key) {
// 取消选中
setSelectedKey(null); setSelectedKey(null);
onChange(null); onChange(null);
logger.debug('TradingTimeFilter', '取消选中', { key: config.key }); logger.debug("TradingTimeFilter", "取消选中", { key: config.key });
} else { } else {
// 选中
setSelectedKey(config.key); setSelectedKey(config.key);
const timeConfig = { onChange({
range: config.range, range: config.range,
type: config.type, type: config.type,
label: config.label, label: config.label,
key: config.key key: config.key,
}; });
onChange(timeConfig); logger.debug("TradingTimeFilter", "选中新按钮", { key: config.key });
logger.debug('TradingTimeFilter', '选中新按钮', { timeConfig });
} }
}; };
@@ -241,27 +86,29 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
const handleCustomRangeOk = (dates) => { const handleCustomRangeOk = (dates) => {
if (dates && dates.length === 2) { if (dates && dates.length === 2) {
setCustomRange(dates); setCustomRange(dates);
setSelectedKey('custom'); setSelectedKey("custom");
setCustomRangeVisible(false); setCustomRangeVisible(false);
onChange({ onChange({
range: dates, range: dates,
type: 'precise', type: "precise",
label: `${dates[0].format('MM-DD HH:mm')} - ${dates[1].format('MM-DD HH:mm')}`, label: `${dates[0].format("MM-DD HH:mm")} - ${dates[1].format("MM-DD HH:mm")}`,
key: 'custom' key: "custom",
}); });
logger.debug('TradingTimeFilter', '自定义范围', { logger.debug("TradingTimeFilter", "自定义范围", {
start: dates[0].format('YYYY-MM-DD HH:mm:ss'), start: dates[0].format("YYYY-MM-DD HH:mm:ss"),
end: dates[1].format('YYYY-MM-DD HH:mm:ss') end: dates[1].format("YYYY-MM-DD HH:mm:ss"),
}); });
} }
}; };
// 渲染紧凑模式按钮PC 端搜索栏内使用,文字按钮 + | 分隔符) // 渲染紧凑模式按钮
const renderCompactButton = (config, showDivider = true) => { const renderCompactButton = (config, showDivider = true) => {
const isSelected = selectedKey === config.key; const isSelected = selectedKey === config.key;
const fullTooltip = config.timeHint ? `${config.tooltip} · ${config.timeHint}` : config.tooltip; const fullTooltip = config.timeHint
? `${config.tooltip} · ${config.timeHint}`
: config.tooltip;
return ( return (
<React.Fragment key={config.key}> <React.Fragment key={config.key}>
@@ -269,41 +116,51 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
<span <span
onClick={() => handleButtonClick(config)} onClick={() => handleButtonClick(config)}
style={{ style={{
cursor: 'pointer', cursor: "pointer",
padding: '4px 8px', padding: "4px 8px",
borderRadius: '4px', borderRadius: "4px",
fontSize: '13px', fontSize: "13px",
fontWeight: isSelected ? 600 : 400, fontWeight: isSelected ? 600 : 400,
color: isSelected ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.text.secondary, color: isSelected
background: isSelected ? 'rgba(255, 195, 0, 0.15)' : 'transparent', ? PROFESSIONAL_COLORS.gold[500]
transition: 'all 0.2s ease', : PROFESSIONAL_COLORS.text.secondary,
whiteSpace: 'nowrap', background: isSelected
? "rgba(255, 195, 0, 0.15)"
: "transparent",
transition: "all 0.2s ease",
whiteSpace: "nowrap",
}} }}
> >
{config.label} {config.label}
</span> </span>
</Tooltip> </Tooltip>
{showDivider && ( {showDivider && (
<span style={{ color: 'rgba(255, 255, 255, 0.2)', margin: '0 2px' }}>|</span> <span style={{ color: "rgba(255, 255, 255, 0.2)", margin: "0 2px" }}>
|
</span>
)} )}
</React.Fragment> </React.Fragment>
); );
}; };
// 渲染按钮(默认模式) // 渲染默认按钮
const renderButton = (config) => { const renderButton = (config) => {
const isSelected = selectedKey === config.key; const isSelected = selectedKey === config.key;
const fullTooltip = config.timeHint
// 构建完整的 tooltip 提示(文字 + 时间) ? `${config.tooltip} · ${config.timeHint}`
const fullTooltip = config.timeHint ? `${config.tooltip} · ${config.timeHint}` : config.tooltip; : config.tooltip;
if (isSelected) { if (isSelected) {
// 选中状态:只显示 Tag不显示下方时间
return ( return (
<Tooltip title={fullTooltip} key={config.key}> <Tooltip title={fullTooltip} key={config.key}>
<Tag <Tag
color={config.color} color={config.color}
style={{ margin: 0, fontSize: 13, padding: '2px 8px', cursor: 'pointer' }} style={{
margin: 0,
fontSize: 13,
padding: "2px 8px",
cursor: "pointer",
}}
onClick={() => handleButtonClick(config)} onClick={() => handleButtonClick(config)}
> >
<ClockCircleOutlined style={{ marginRight: 4 }} /> <ClockCircleOutlined style={{ marginRight: 4 }} />
@@ -312,7 +169,6 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
</Tooltip> </Tooltip>
); );
} else { } else {
// 未选中状态:普通按钮
return ( return (
<Tooltip title={fullTooltip} key={config.key}> <Tooltip title={fullTooltip} key={config.key}>
<Button <Button
@@ -327,75 +183,36 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
} }
}; };
// 禁用未来日期 // 自定义时间选择器内容
const disabledDate = (current) => {
return current && current > dayjs().endOf('day');
};
// 禁用未来时间(精确到分钟)
const disabledTime = (current) => {
if (!current) return {};
const now = dayjs();
const isToday = current.isSame(now, 'day');
if (!isToday) return {};
const currentHour = now.hour();
const currentMinute = now.minute();
return {
disabledHours: () => {
const hours = [];
for (let i = currentHour + 1; i < 24; i++) {
hours.push(i);
}
return hours;
},
disabledMinutes: (selectedHour) => {
if (selectedHour === currentHour) {
const minutes = [];
for (let i = currentMinute + 1; i < 60; i++) {
minutes.push(i);
}
return minutes;
}
return [];
}
};
};
// "更多时间" 按钮内容
const customRangeContent = ( const customRangeContent = (
<div style={{ padding: 8 }}> <div style={{ padding: 8 }}>
<RangePicker <RangePicker
showTime={{ format: 'HH:mm' }} showTime={{ format: "HH:mm" }}
format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm"
locale={locale} locale={locale}
placeholder={['开始时间', '结束时间']} placeholder={["开始时间", "结束时间"]}
onChange={handleCustomRangeOk} onChange={handleCustomRangeOk}
value={customRange} value={customRange}
disabledDate={disabledDate} disabledDate={disabledDate}
disabledTime={disabledTime} disabledTime={disabledTime}
style={{ marginBottom: 8 }} style={{ marginBottom: 8 }}
/> />
<div style={{ fontSize: 12, color: '#999', marginTop: 4 }}> <div style={{ fontSize: 12, color: "#999", marginTop: 4 }}>
支持精确到分钟的时间范围选择不能超过当前时间 支持精确到分钟的时间范围选择不能超过当前时间
</div> </div>
</div> </div>
); );
// 移动端模式:下拉选择器 // 移动端模式
if (mobile) { if (mobile) {
const allButtons = [...timeRangeConfig.dynamic, ...timeRangeConfig.fixed]; const allButtons = [...timeRangeConfig.dynamic, ...timeRangeConfig.fixed];
const handleMobileSelect = (key) => { const handleMobileSelect = (key) => {
if (key === selectedKey) { if (key === selectedKey) {
// 取消选中
setSelectedKey(null); setSelectedKey(null);
onChange(null); onChange(null);
} else { } else {
const config = allButtons.find(b => b.key === key); const config = allButtons.find((b) => b.key === key);
if (config) { if (config) {
handleButtonClick(config); handleButtonClick(config);
} }
@@ -407,7 +224,7 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
value={selectedKey} value={selectedKey}
onChange={handleMobileSelect} onChange={handleMobileSelect}
placeholder={ placeholder={
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}> <span style={{ display: "flex", alignItems: "center", gap: 4 }}>
<FilterOutlined style={{ fontSize: 12 }} /> <FilterOutlined style={{ fontSize: 12 }} />
筛选 筛选
</span> </span>
@@ -421,7 +238,7 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
className="transparent-select" className="transparent-select"
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
> >
{allButtons.map(config => ( {allButtons.map((config) => (
<Option key={config.key} value={config.key}> <Option key={config.key} value={config.key}>
{config.label} {config.label}
</Option> </Option>
@@ -430,18 +247,20 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
); );
} }
// 紧凑模式PC 端搜索栏内的样式 // 紧凑模式
if (compact) { if (compact) {
// 合并所有按钮配置
const allButtons = [...timeRangeConfig.dynamic, ...timeRangeConfig.fixed]; const allButtons = [...timeRangeConfig.dynamic, ...timeRangeConfig.fixed];
return ( return (
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'nowrap' }}> <div
style={{ display: "flex", alignItems: "center", flexWrap: "nowrap" }}
>
{allButtons.map((config, index) => {allButtons.map((config, index) =>
renderCompactButton(config, index < allButtons.length - 1) renderCompactButton(config, index < allButtons.length - 1)
)} )}
{/* 更多时间 */} <span style={{ color: "rgba(255, 255, 255, 0.2)", margin: "0 2px" }}>
<span style={{ color: 'rgba(255, 255, 255, 0.2)', margin: '0 2px' }}>|</span> |
</span>
<Popover <Popover
content={customRangeContent} content={customRangeContent}
title="选择自定义时间范围" title="选择自定义时间范围"
@@ -453,18 +272,24 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
<Tooltip title="自定义时间范围"> <Tooltip title="自定义时间范围">
<span <span
style={{ style={{
cursor: 'pointer', cursor: "pointer",
padding: '4px 8px', padding: "4px 8px",
borderRadius: '4px', borderRadius: "4px",
fontSize: '13px', fontSize: "13px",
fontWeight: selectedKey === 'custom' ? 600 : 400, fontWeight: selectedKey === "custom" ? 600 : 400,
color: selectedKey === 'custom' ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.text.secondary, color:
background: selectedKey === 'custom' ? 'rgba(255, 195, 0, 0.15)' : 'transparent', selectedKey === "custom"
transition: 'all 0.2s ease', ? PROFESSIONAL_COLORS.gold[500]
whiteSpace: 'nowrap', : PROFESSIONAL_COLORS.text.secondary,
display: 'flex', background:
alignItems: 'center', selectedKey === "custom"
gap: '4px', ? "rgba(255, 195, 0, 0.15)"
: "transparent",
transition: "all 0.2s ease",
whiteSpace: "nowrap",
display: "flex",
alignItems: "center",
gap: "4px",
}} }}
> >
<CalendarOutlined style={{ fontSize: 12 }} /> <CalendarOutlined style={{ fontSize: 12 }} />
@@ -476,16 +301,16 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
); );
} }
// 默认模式:移动端/独立使用 // 默认模式
return ( return (
<Space wrap size={[8, 8]} style={{ display: 'flex', alignItems: 'flex-start' }}> <Space
{/* 动态按钮(根据时段显示多个) */} wrap
{timeRangeConfig.dynamic.map(config => renderButton(config))} size={[8, 8]}
style={{ display: "flex", alignItems: "flex-start" }}
>
{timeRangeConfig.dynamic.map((config) => renderButton(config))}
{timeRangeConfig.fixed.map((config) => renderButton(config))}
{/* 固定按钮(始终显示) */}
{timeRangeConfig.fixed.map(config => renderButton(config))}
{/* 更多时间 - 自定义范围 */}
<Popover <Popover
content={customRangeContent} content={customRangeContent}
title="选择自定义时间范围" title="选择自定义时间范围"
@@ -494,18 +319,36 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false })
onOpenChange={setCustomRangeVisible} onOpenChange={setCustomRangeVisible}
placement="bottomLeft" placement="bottomLeft"
> >
{selectedKey === 'custom' ? ( {selectedKey === "custom" ? (
<Tooltip <Tooltip
title={customRange ? `自定义时间范围 · ${customRange[0].format('MM-DD HH:mm')} - ${customRange[1].format('MM-DD HH:mm')}` : '自定义时间范围'} 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' }}> <Tag
color="gold"
style={{
margin: 0,
fontSize: 13,
padding: "2px 8px",
cursor: "pointer",
}}
>
<CalendarOutlined style={{ marginRight: 4 }} /> <CalendarOutlined style={{ marginRight: 4 }} />
{customRange ? `${customRange[0].format('MM-DD HH:mm')} - ${customRange[1].format('MM-DD HH:mm')}` : '自定义'} {customRange
? `${customRange[0].format("MM-DD HH:mm")} - ${customRange[1].format("MM-DD HH:mm")}`
: "自定义"}
</Tag> </Tag>
</Tooltip> </Tooltip>
) : ( ) : (
<Tooltip title="自定义时间范围"> <Tooltip title="自定义时间范围">
<Button size="small" icon={<CalendarOutlined />} style={{ fontSize: 12 }}> <Button
size="small"
icon={<CalendarOutlined />}
style={{ fontSize: 12 }}
>
更多时间 更多时间
</Button> </Button>
</Tooltip> </Tooltip>

View File

@@ -0,0 +1,18 @@
// TradingTimeFilter 常量定义
// 时段边界(分钟数)
export const TIME_BOUNDARIES = {
PRE_MARKET_END: 9 * 60 + 30, // 09:30
MORNING_END: 11 * 60 + 30, // 11:30
AFTERNOON_START: 13 * 60, // 13:00
MARKET_CLOSE: 15 * 60, // 15:00
};
// 时段类型
export const TRADING_SESSIONS = {
PRE_MARKET: "pre-market", // 盘前
MORNING: "morning", // 早盘
LUNCH: "lunch", // 午休
AFTERNOON: "afternoon", // 午盘
AFTER_HOURS: "after-hours", // 盘后
};

View File

@@ -0,0 +1,10 @@
// TradingTimeFilter 模块导出
export { TIME_BOUNDARIES, TRADING_SESSIONS } from "./constants";
export {
getCurrentTradingSession,
getPrevTradingDay,
generateTimeRangeConfig,
disabledDate,
disabledTime,
} from "./utils";

View File

@@ -0,0 +1,224 @@
// TradingTimeFilter 工具函数
import dayjs from "dayjs";
import { TIME_BOUNDARIES, TRADING_SESSIONS } from "./constants";
import { logger } from "@utils/logger";
import tradingDayUtils from "@utils/tradingDayUtils";
/**
* 获取当前交易时段
* @returns {string} 时段标识
*/
export const getCurrentTradingSession = () => {
const now = dayjs();
const hour = now.hour();
const minute = now.minute();
const currentMinutes = hour * 60 + minute;
if (currentMinutes < TIME_BOUNDARIES.PRE_MARKET_END) {
return TRADING_SESSIONS.PRE_MARKET;
} else if (currentMinutes < TIME_BOUNDARIES.MORNING_END) {
return TRADING_SESSIONS.MORNING;
} else if (currentMinutes < TIME_BOUNDARIES.AFTERNOON_START) {
return TRADING_SESSIONS.LUNCH;
} else if (currentMinutes < TIME_BOUNDARIES.MARKET_CLOSE) {
return TRADING_SESSIONS.AFTERNOON;
} else {
return TRADING_SESSIONS.AFTER_HOURS;
}
};
/**
* 获取上一个交易日
* @param {dayjs.Dayjs} now - 当前时间
* @returns {dayjs.Dayjs} 上一交易日
*/
export const getPrevTradingDay = (now) => {
try {
const prevTradingDay = tradingDayUtils.getPreviousTradingDay(now.toDate());
return dayjs(prevTradingDay);
} catch (e) {
logger.warn("TradingTimeFilter", "获取上一交易日失败,降级处理", e);
return now.subtract(1, "day");
}
};
/**
* 生成时间范围配置
* @returns {Object} 包含 dynamic 和 fixed 按钮配置
*/
export const generateTimeRangeConfig = () => {
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 yesterdayStart = now.subtract(1, "day").startOf("day");
const yesterdayEnd = now.subtract(1, "day").endOf("day");
// 动态按钮配置(根据时段)
const dynamicButtonsMap = {
[TRADING_SESSIONS.PRE_MARKET]: [],
[TRADING_SESSIONS.MORNING]: [
{
key: "intraday",
label: "盘中",
range: [today0930, today1500],
tooltip: "盘中交易时段",
timeHint: "今日 09:30 - 15:00",
color: "blue",
type: "precise",
},
],
[TRADING_SESSIONS.LUNCH]: [],
[TRADING_SESSIONS.AFTERNOON]: [
{
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",
},
],
[TRADING_SESSIONS.AFTER_HOURS]: [],
};
const prevTradingDay = getPrevTradingDay(now);
const prevTradingDay1500 = prevTradingDay.hour(15).minute(0).second(0);
// 固定按钮配置
const fixedButtons = [
{
key: "current-trading-day",
label: "当前交易日",
range: [prevTradingDay1500, now],
tooltip: "当前交易日事件",
timeHint: `${prevTradingDay.format("MM-DD")} 15:00 - 现在`,
color: "green",
type: "precise",
},
{
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",
},
{
key: "all",
label: "全部",
range: null,
tooltip: "显示全部事件",
timeHint: "不限时间",
color: "default",
type: "all",
},
];
return {
dynamic: dynamicButtonsMap[session] || [],
fixed: fixedButtons,
};
};
/**
* 禁用未来日期
* @param {dayjs.Dayjs} current - 当前日期
* @returns {boolean} 是否禁用
*/
export const disabledDate = (current) => {
return current && current > dayjs().endOf("day");
};
/**
* 禁用未来时间(精确到分钟)
* @param {dayjs.Dayjs} current - 当前时间
* @returns {Object} 禁用配置
*/
export const disabledTime = (current) => {
if (!current) return {};
const now = dayjs();
const isToday = current.isSame(now, "day");
if (!isToday) return {};
const currentHour = now.hour();
const currentMinute = now.minute();
return {
disabledHours: () => {
const hours = [];
for (let i = currentHour + 1; i < 24; i++) {
hours.push(i);
}
return hours;
},
disabledMinutes: (selectedHour) => {
if (selectedHour === currentHour) {
const minutes = [];
for (let i = currentMinute + 1; i < 60; i++) {
minutes.push(i);
}
return minutes;
}
return [];
},
};
};