263 lines
6.3 KiB
JavaScript
263 lines
6.3 KiB
JavaScript
// 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;
|