update pay ui

This commit is contained in:
2025-12-02 10:33:55 +08:00
parent 2395d92b17
commit 7708cb1a69
4 changed files with 473 additions and 44 deletions

261
src/hooks/useIndexQuote.js Normal file
View File

@@ -0,0 +1,261 @@
// src/hooks/useIndexQuote.js
// 指数实时行情 Hook - 交易时间内每分钟自动更新
import { useState, useEffect, useCallback, useRef } from 'react';
import { logger } from '../utils/logger';
// 交易日数据会从后端获取,这里只做时间判断
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(`/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 - 刷新间隔(毫秒),默认 600001分钟
* @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;