From da13cf08c5fdb2bf687305afd7d4f14d7aa20375 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Thu, 15 Jan 2026 11:45:12 +0800 Subject: [PATCH] =?UTF-8?q?refactor(TradingTimeFilter):=20=E6=8F=90?= =?UTF-8?q?=E5=8F=96=E5=B8=B8=E9=87=8F=E5=92=8C=E5=B7=A5=E5=85=B7=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E5=88=B0=E5=AD=90=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../SearchFilters/TradingTimeFilter.js | 443 ++++++------------ .../TradingTimeFilter/constants.js | 18 + .../SearchFilters/TradingTimeFilter/index.js | 10 + .../SearchFilters/TradingTimeFilter/utils.js | 224 +++++++++ 4 files changed, 395 insertions(+), 300 deletions(-) create mode 100644 src/views/Community/components/SearchFilters/TradingTimeFilter/constants.js create mode 100644 src/views/Community/components/SearchFilters/TradingTimeFilter/index.js create mode 100644 src/views/Community/components/SearchFilters/TradingTimeFilter/utils.js diff --git a/src/views/Community/components/SearchFilters/TradingTimeFilter.js b/src/views/Community/components/SearchFilters/TradingTimeFilter.js index 81728152..68dfed13 100644 --- a/src/views/Community/components/SearchFilters/TradingTimeFilter.js +++ b/src/views/Community/components/SearchFilters/TradingTimeFilter.js @@ -1,239 +1,84 @@ // src/views/Community/components/TradingTimeFilter.js // 交易时段智能筛选组件 -import React, { useState, useMemo, useEffect } from 'react'; -import { Space, Button, Tag, Tooltip, DatePicker, Popover, Select } from 'antd'; -import { ClockCircleOutlined, CalendarOutlined, FilterOutlined } from '@ant-design/icons'; -import dayjs from 'dayjs'; -import locale from 'antd/es/date-picker/locale/zh_CN'; -import { logger } from '@utils/logger'; -import { PROFESSIONAL_COLORS } from '@constants/professionalTheme'; -import tradingDayUtils from '@utils/tradingDayUtils'; + +import React, { useState, useMemo, useEffect } from "react"; +import { + Space, + Button, + Tag, + Tooltip, + 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 { 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 [customRangeVisible, setCustomRangeVisible] = useState(false); const [customRange, setCustomRange] = useState(null); - // 监听外部 value 变化,同步内部状态 + // 监听外部 value 变化 useEffect(() => { if (value === null || value === undefined) { - // 外部重置,清空内部状态 setSelectedKey(null); setCustomRange(null); - logger.debug('TradingTimeFilter', '外部重置,清空选中状态'); + logger.debug("TradingTimeFilter", "外部重置,清空选中状态"); } else { - // 外部选中值变化,同步内部状态 setSelectedKey(value); - logger.debug('TradingTimeFilter', '外部value变化,同步内部状态', { value }); + logger.debug("TradingTimeFilter", "外部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 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 timeRangeConfig = useMemo(() => generateTimeRangeConfig(), []); // 按钮点击处理 const handleButtonClick = (config) => { - logger.debug('TradingTimeFilter', '按钮点击', { + logger.debug("TradingTimeFilter", "按钮点击", { config, currentSelectedKey: selectedKey, - willToggle: selectedKey === config.key + willToggle: selectedKey === config.key, }); if (selectedKey === config.key) { - // 取消选中 setSelectedKey(null); onChange(null); - logger.debug('TradingTimeFilter', '取消选中', { key: config.key }); + logger.debug("TradingTimeFilter", "取消选中", { key: config.key }); } else { - // 选中 setSelectedKey(config.key); - const timeConfig = { + onChange({ range: config.range, type: config.type, label: config.label, - key: config.key - }; - onChange(timeConfig); - logger.debug('TradingTimeFilter', '选中新按钮', { timeConfig }); + key: config.key, + }); + logger.debug("TradingTimeFilter", "选中新按钮", { key: config.key }); } }; @@ -241,27 +86,29 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false }) const handleCustomRangeOk = (dates) => { if (dates && dates.length === 2) { setCustomRange(dates); - setSelectedKey('custom'); + 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' + 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') + logger.debug("TradingTimeFilter", "自定义范围", { + start: dates[0].format("YYYY-MM-DD HH:mm:ss"), + end: dates[1].format("YYYY-MM-DD HH:mm:ss"), }); } }; - // 渲染紧凑模式按钮(PC 端搜索栏内使用,文字按钮 + | 分隔符) + // 渲染紧凑模式按钮 const renderCompactButton = (config, showDivider = true) => { 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 ( @@ -269,41 +116,51 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false }) handleButtonClick(config)} style={{ - cursor: 'pointer', - padding: '4px 8px', - borderRadius: '4px', - fontSize: '13px', + cursor: "pointer", + padding: "4px 8px", + borderRadius: "4px", + fontSize: "13px", fontWeight: isSelected ? 600 : 400, - color: isSelected ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.text.secondary, - background: isSelected ? 'rgba(255, 195, 0, 0.15)' : 'transparent', - transition: 'all 0.2s ease', - whiteSpace: 'nowrap', + color: isSelected + ? PROFESSIONAL_COLORS.gold[500] + : PROFESSIONAL_COLORS.text.secondary, + background: isSelected + ? "rgba(255, 195, 0, 0.15)" + : "transparent", + transition: "all 0.2s ease", + whiteSpace: "nowrap", }} > {config.label} {showDivider && ( - | + + | + )} ); }; - // 渲染按钮(默认模式) + // 渲染默认按钮 const renderButton = (config) => { const isSelected = selectedKey === config.key; - - // 构建完整的 tooltip 提示(文字 + 时间) - const fullTooltip = config.timeHint ? `${config.tooltip} · ${config.timeHint}` : config.tooltip; + const fullTooltip = config.timeHint + ? `${config.tooltip} · ${config.timeHint}` + : config.tooltip; if (isSelected) { - // 选中状态:只显示 Tag,不显示下方时间 return ( handleButtonClick(config)} > @@ -312,7 +169,6 @@ const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false }) ); } else { - // 未选中状态:普通按钮 return ( diff --git a/src/views/Community/components/SearchFilters/TradingTimeFilter/constants.js b/src/views/Community/components/SearchFilters/TradingTimeFilter/constants.js new file mode 100644 index 00000000..01c9c01e --- /dev/null +++ b/src/views/Community/components/SearchFilters/TradingTimeFilter/constants.js @@ -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", // 盘后 +}; diff --git a/src/views/Community/components/SearchFilters/TradingTimeFilter/index.js b/src/views/Community/components/SearchFilters/TradingTimeFilter/index.js new file mode 100644 index 00000000..01a4f735 --- /dev/null +++ b/src/views/Community/components/SearchFilters/TradingTimeFilter/index.js @@ -0,0 +1,10 @@ +// TradingTimeFilter 模块导出 + +export { TIME_BOUNDARIES, TRADING_SESSIONS } from "./constants"; +export { + getCurrentTradingSession, + getPrevTradingDay, + generateTimeRangeConfig, + disabledDate, + disabledTime, +} from "./utils"; diff --git a/src/views/Community/components/SearchFilters/TradingTimeFilter/utils.js b/src/views/Community/components/SearchFilters/TradingTimeFilter/utils.js new file mode 100644 index 00000000..4fadd81a --- /dev/null +++ b/src/views/Community/components/SearchFilters/TradingTimeFilter/utils.js @@ -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 []; + }, + }; +};