// src/hooks/useIndexQuote.js // 指数实时行情 Hook - 交易时间内每分钟自动更新 import { useState, useEffect, useCallback, useRef } from 'react'; import { logger } from '../utils/logger'; import { getApiBase } from '../utils/apiConfig'; // 交易日数据会从后端获取,这里只做时间判断 const TRADING_SESSIONS = [ { start: { hour: 9, minute: 30 }, end: { hour: 11, minute: 30 } }, { start: { hour: 13, minute: 0 }, end: { hour: 15, minute: 0 } }, ]; /** * 判断当前时间是否在交易时段内 */ const isInTradingSession = () => { const now = new Date(); const currentMinutes = now.getHours() * 60 + now.getMinutes(); return TRADING_SESSIONS.some(session => { const startMinutes = session.start.hour * 60 + session.start.minute; const endMinutes = session.end.hour * 60 + session.end.minute; return currentMinutes >= startMinutes && currentMinutes <= endMinutes; }); }; /** * 获取指数实时行情 */ const fetchIndexRealtime = async (indexCode) => { try { const response = await fetch(`${getApiBase()}/api/index/${indexCode}/realtime`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); if (result.success && result.data) { return result.data; } return null; } catch (error) { logger.error('useIndexQuote', 'fetchIndexRealtime error', { indexCode, error: error.message }); return null; } }; /** * 指数实时行情 Hook * * @param {string} indexCode - 指数代码,如 '000001' (上证指数) 或 '399001' (深证成指) * @param {Object} options - 配置选项 * @param {number} options.refreshInterval - 刷新间隔(毫秒),默认 60000(1分钟) * @param {boolean} options.autoRefresh - 是否自动刷新,默认 true * * @returns {Object} { quote, loading, error, isTrading, refresh } */ export const useIndexQuote = (indexCode, options = {}) => { const { refreshInterval = 60000, // 默认1分钟 autoRefresh = true, } = options; const [quote, setQuote] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [isTrading, setIsTrading] = useState(false); const intervalRef = useRef(null); const isMountedRef = useRef(true); // 加载数据 const loadQuote = useCallback(async () => { if (!indexCode) return; try { const data = await fetchIndexRealtime(indexCode); if (!isMountedRef.current) return; if (data) { setQuote(data); setIsTrading(data.is_trading); setError(null); } else { setError('无法获取行情数据'); } } catch (err) { if (isMountedRef.current) { setError(err.message); } } finally { if (isMountedRef.current) { setLoading(false); } } }, [indexCode]); // 手动刷新 const refresh = useCallback(() => { setLoading(true); loadQuote(); }, [loadQuote]); // 初始加载 useEffect(() => { isMountedRef.current = true; loadQuote(); return () => { isMountedRef.current = false; }; }, [loadQuote]); // 自动刷新逻辑 useEffect(() => { if (!autoRefresh || !indexCode) return; // 清除旧的定时器 if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } // 设置定时器,检查是否在交易时间内 const checkAndRefresh = () => { const inSession = isInTradingSession(); setIsTrading(inSession); if (inSession) { loadQuote(); } }; // 立即检查一次 checkAndRefresh(); // 设置定时刷新 intervalRef.current = setInterval(checkAndRefresh, refreshInterval); return () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; }, [autoRefresh, indexCode, refreshInterval, loadQuote]); return { quote, loading, error, isTrading, refresh, }; }; /** * 批量获取多个指数的实时行情 * * @param {string[]} indexCodes - 指数代码数组 * @param {Object} options - 配置选项 */ export const useMultiIndexQuotes = (indexCodes = [], options = {}) => { const { refreshInterval = 60000, autoRefresh = true, } = options; const [quotes, setQuotes] = useState({}); const [loading, setLoading] = useState(true); const [isTrading, setIsTrading] = useState(false); const intervalRef = useRef(null); const isMountedRef = useRef(true); // 批量加载数据 const loadQuotes = useCallback(async () => { if (!indexCodes || indexCodes.length === 0) return; try { const results = await Promise.all( indexCodes.map(code => fetchIndexRealtime(code)) ); if (!isMountedRef.current) return; const newQuotes = {}; let hasTrading = false; results.forEach((data, idx) => { if (data) { newQuotes[indexCodes[idx]] = data; if (data.is_trading) hasTrading = true; } }); setQuotes(newQuotes); setIsTrading(hasTrading); } catch (err) { logger.error('useMultiIndexQuotes', 'loadQuotes error', err); } finally { if (isMountedRef.current) { setLoading(false); } } }, [indexCodes]); // 手动刷新 const refresh = useCallback(() => { setLoading(true); loadQuotes(); }, [loadQuotes]); // 初始加载 useEffect(() => { isMountedRef.current = true; loadQuotes(); return () => { isMountedRef.current = false; }; }, [loadQuotes]); // 自动刷新逻辑 useEffect(() => { if (!autoRefresh || indexCodes.length === 0) return; if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } const checkAndRefresh = () => { const inSession = isInTradingSession(); setIsTrading(inSession); if (inSession) { loadQuotes(); } }; checkAndRefresh(); intervalRef.current = setInterval(checkAndRefresh, refreshInterval); return () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; }, [autoRefresh, indexCodes, refreshInterval, loadQuotes]); return { quotes, loading, isTrading, refresh, }; }; export default useIndexQuote;