update pay ui

This commit is contained in:
2025-12-03 13:06:23 +08:00
parent 5042d1ee46
commit 9a6230e51e
3 changed files with 123 additions and 46 deletions

View File

@@ -15,9 +15,11 @@ import {
* @param {string} stockCode - 股票代码 * @param {string} stockCode - 股票代码
* @param {string} eventTime - 事件时间(可选) * @param {string} eventTime - 事件时间(可选)
* @param {Function} onClick - 点击回调(可选) * @param {Function} onClick - 点击回调(可选)
* @param {Array} preloadedData - 预加载的K线数据可选由父组件批量加载后传入
* @param {boolean} loading - 外部加载状态(可选)
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
const MiniKLineChart = React.memo(function MiniKLineChart({ stockCode, eventTime, onClick }) { const MiniKLineChart = React.memo(function MiniKLineChart({ stockCode, eventTime, onClick, preloadedData, loading: externalLoading }) {
const [data, setData] = useState([]); const [data, setData] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const mountedRef = useRef(true); const mountedRef = useRef(true);
@@ -44,6 +46,21 @@ const MiniKLineChart = React.memo(function MiniKLineChart({ stockCode, eventTime
return; return;
} }
// 优先使用预加载的数据(由父组件批量请求后传入)
if (preloadedData !== undefined) {
setData(preloadedData || []);
setLoading(false);
loadedRef.current = true;
dataFetchedRef.current = true;
return;
}
// 如果外部正在加载显示loading状态不发起单独请求
if (externalLoading) {
setLoading(true);
return;
}
if (dataFetchedRef.current) { if (dataFetchedRef.current) {
return; return;
} }
@@ -52,8 +69,8 @@ const MiniKLineChart = React.memo(function MiniKLineChart({ stockCode, eventTime
const cacheKey = getCacheKey(stockCode, stableEventTime, 'daily'); const cacheKey = getCacheKey(stockCode, stableEventTime, 'daily');
const cachedData = klineDataCache.get(cacheKey); const cachedData = klineDataCache.get(cacheKey);
if (cachedData && cachedData.length > 0) { if (cachedData !== undefined) {
setData(cachedData); setData(cachedData || []);
loadedRef.current = true; loadedRef.current = true;
dataFetchedRef.current = true; dataFetchedRef.current = true;
return; return;
@@ -62,7 +79,7 @@ const MiniKLineChart = React.memo(function MiniKLineChart({ stockCode, eventTime
dataFetchedRef.current = true; dataFetchedRef.current = true;
setLoading(true); setLoading(true);
// 获取日K线数据 // 获取日K线数据(备用方案)
fetchKlineData(stockCode, stableEventTime, 'daily') fetchKlineData(stockCode, stableEventTime, 'daily')
.then((result) => { .then((result) => {
if (mountedRef.current) { if (mountedRef.current) {
@@ -78,7 +95,7 @@ const MiniKLineChart = React.memo(function MiniKLineChart({ stockCode, eventTime
loadedRef.current = true; loadedRef.current = true;
} }
}); });
}, [stockCode, stableEventTime]); }, [stockCode, stableEventTime, preloadedData, externalLoading]);
const chartOption = useMemo(() => { const chartOption = useMemo(() => {
// 提取K线数据 [open, close, low, high] // 提取K线数据 [open, close, low, high]
@@ -179,7 +196,9 @@ const MiniKLineChart = React.memo(function MiniKLineChart({ stockCode, eventTime
}, (prevProps, nextProps) => { }, (prevProps, nextProps) => {
return prevProps.stockCode === nextProps.stockCode && return prevProps.stockCode === nextProps.stockCode &&
prevProps.eventTime === nextProps.eventTime && prevProps.eventTime === nextProps.eventTime &&
prevProps.onClick === nextProps.onClick; prevProps.onClick === nextProps.onClick &&
prevProps.preloadedData === nextProps.preloadedData &&
prevProps.loading === nextProps.loading;
}); });
export default MiniKLineChart; export default MiniKLineChart;

View File

@@ -25,9 +25,13 @@ const RelatedStocksSection = ({
watchlistSet = new Set(), watchlistSet = new Set(),
onWatchlistToggle onWatchlistToggle
}) => { }) => {
// K线数据状态:{ [stockCode]: data[] } // 分时图数据状态:{ [stockCode]: data[] }
const [klineDataMap, setKlineDataMap] = useState({}); const [timelineDataMap, setTimelineDataMap] = useState({});
const [klineLoading, setKlineLoading] = useState(false); const [timelineLoading, setTimelineLoading] = useState(false);
// 日K线数据状态{ [stockCode]: data[] }
const [dailyDataMap, setDailyDataMap] = useState({});
const [dailyLoading, setDailyLoading] = useState(false);
// 稳定的事件时间 // 稳定的事件时间
const stableEventTime = useMemo(() => { const stableEventTime = useMemo(() => {
@@ -40,67 +44,113 @@ const RelatedStocksSection = ({
return stocks.map(s => s.stock_code).sort().join(','); return stocks.map(s => s.stock_code).sort().join(',');
}, [stocks]); }, [stocks]);
// 计算是否应该显示 loading // 计算分时图是否应该显示 loading
const shouldShowLoading = useMemo(() => { const shouldShowTimelineLoading = useMemo(() => {
if (!stocks || stocks.length === 0) return false; if (!stocks || stocks.length === 0) return false;
const currentDataKeys = Object.keys(klineDataMap).sort().join(','); const currentDataKeys = Object.keys(timelineDataMap).sort().join(',');
if (stocksKey !== currentDataKeys) { if (stocksKey !== currentDataKeys) {
return true; return true;
} }
return klineLoading; return timelineLoading;
}, [stocks, stocksKey, klineDataMap, klineLoading]); }, [stocks, stocksKey, timelineDataMap, timelineLoading]);
// 批量加载K线数据 // 计算日K线是否应该显示 loading
const shouldShowDailyLoading = useMemo(() => {
if (!stocks || stocks.length === 0) return false;
const currentDataKeys = Object.keys(dailyDataMap).sort().join(',');
if (stocksKey !== currentDataKeys) {
return true;
}
return dailyLoading;
}, [stocks, stocksKey, dailyDataMap, dailyLoading]);
// 批量加载分时图数据
useEffect(() => { useEffect(() => {
if (!stocks || stocks.length === 0) { if (!stocks || stocks.length === 0) {
setKlineDataMap({}); setTimelineDataMap({});
setKlineLoading(false); setTimelineLoading(false);
return; return;
} }
// 立即设置 loading 状态 setTimelineLoading(true);
setKlineLoading(true);
const stockCodes = stocks.map(s => s.stock_code); const stockCodes = stocks.map(s => s.stock_code);
// 检查缓存 // 检查缓存
const cachedData = {}; const cachedData = {};
const uncachedCodes = [];
stockCodes.forEach(code => { stockCodes.forEach(code => {
const cacheKey = getCacheKey(code, stableEventTime, 'timeline'); const cacheKey = getCacheKey(code, stableEventTime, 'timeline');
const cached = klineDataCache.get(cacheKey); const cached = klineDataCache.get(cacheKey);
if (cached !== undefined) { if (cached !== undefined) {
cachedData[code] = cached; cachedData[code] = cached;
} else {
uncachedCodes.push(code);
} }
}); });
// 如果全部缓存命中,直接使用 if (Object.keys(cachedData).length === stockCodes.length) {
if (uncachedCodes.length === 0) { setTimelineDataMap(cachedData);
setKlineDataMap(cachedData); setTimelineLoading(false);
setKlineLoading(false); logger.debug('RelatedStocksSection', '分时图数据全部来自缓存', { stockCount: stockCodes.length });
logger.debug('RelatedStocksSection', 'K线数据全部来自缓存', { stockCount: stockCodes.length });
return; return;
} }
logger.debug('RelatedStocksSection', '批量加载K线数据', { logger.debug('RelatedStocksSection', '批量加载分时图数据', {
totalCount: stockCodes.length, totalCount: stockCodes.length,
cachedCount: Object.keys(cachedData).length,
uncachedCount: uncachedCodes.length,
eventTime: stableEventTime eventTime: stableEventTime
}); });
// 批量请求
fetchBatchKlineData(stockCodes, stableEventTime, 'timeline') fetchBatchKlineData(stockCodes, stableEventTime, 'timeline')
.then((batchData) => { .then((batchData) => {
setKlineDataMap({ ...cachedData, ...batchData }); setTimelineDataMap({ ...cachedData, ...batchData });
setKlineLoading(false); setTimelineLoading(false);
}) })
.catch((error) => { .catch((error) => {
logger.error('RelatedStocksSection', '批量加载K线数据失败', error); logger.error('RelatedStocksSection', '批量加载分时图数据失败', error);
setKlineDataMap(cachedData); setTimelineDataMap(cachedData);
setKlineLoading(false); setTimelineLoading(false);
});
}, [stocksKey, stableEventTime]);
// 批量加载日K线数据
useEffect(() => {
if (!stocks || stocks.length === 0) {
setDailyDataMap({});
setDailyLoading(false);
return;
}
setDailyLoading(true);
const stockCodes = stocks.map(s => s.stock_code);
// 检查缓存
const cachedData = {};
stockCodes.forEach(code => {
const cacheKey = getCacheKey(code, stableEventTime, 'daily');
const cached = klineDataCache.get(cacheKey);
if (cached !== undefined) {
cachedData[code] = cached;
}
});
if (Object.keys(cachedData).length === stockCodes.length) {
setDailyDataMap(cachedData);
setDailyLoading(false);
logger.debug('RelatedStocksSection', '日K线数据全部来自缓存', { stockCount: stockCodes.length });
return;
}
logger.debug('RelatedStocksSection', '批量加载日K线数据', {
totalCount: stockCodes.length,
eventTime: stableEventTime
});
fetchBatchKlineData(stockCodes, stableEventTime, 'daily')
.then((batchData) => {
setDailyDataMap({ ...cachedData, ...batchData });
setDailyLoading(false);
})
.catch((error) => {
logger.error('RelatedStocksSection', '批量加载日K线数据失败', error);
setDailyDataMap(cachedData);
setDailyLoading(false);
}); });
}, [stocksKey, stableEventTime]); }, [stocksKey, stableEventTime]);
@@ -119,8 +169,10 @@ const RelatedStocksSection = ({
eventTime={eventTime} eventTime={eventTime}
isInWatchlist={watchlistSet.has(stock.stock_code)} isInWatchlist={watchlistSet.has(stock.stock_code)}
onWatchlistToggle={onWatchlistToggle} onWatchlistToggle={onWatchlistToggle}
klineData={klineDataMap[stock.stock_code]} timelineData={timelineDataMap[stock.stock_code]}
klineLoading={shouldShowLoading && !klineDataMap[stock.stock_code]} timelineLoading={shouldShowTimelineLoading && !timelineDataMap[stock.stock_code]}
dailyData={dailyDataMap[stock.stock_code]}
dailyLoading={shouldShowDailyLoading && !dailyDataMap[stock.stock_code]}
/> />
))} ))}
</VStack> </VStack>

View File

@@ -39,8 +39,10 @@ import { PROFESSIONAL_COLORS } from '@constants/professionalTheme';
* @param {string} props.eventTime - 事件时间(可选) * @param {string} props.eventTime - 事件时间(可选)
* @param {boolean} props.isInWatchlist - 是否在自选股中 * @param {boolean} props.isInWatchlist - 是否在自选股中
* @param {Function} props.onWatchlistToggle - 切换自选股回调 * @param {Function} props.onWatchlistToggle - 切换自选股回调
* @param {Array} props.klineData - 预加载的K线数据(可选,由父组件批量加载后传入) * @param {Array} props.timelineData - 预加载的分时图数据(可选,由父组件批量加载后传入)
* @param {boolean} props.klineLoading - K线数据加载状态 * @param {boolean} props.timelineLoading - 分时图数据加载状态
* @param {Array} props.dailyData - 预加载的日K线数据可选由父组件批量加载后传入
* @param {boolean} props.dailyLoading - 日K线数据加载状态
*/ */
const StockListItem = ({ const StockListItem = ({
stock, stock,
@@ -48,8 +50,10 @@ const StockListItem = ({
eventTime = null, eventTime = null,
isInWatchlist = false, isInWatchlist = false,
onWatchlistToggle, onWatchlistToggle,
klineData, timelineData,
klineLoading = false timelineLoading = false,
dailyData,
dailyLoading = false
}) => { }) => {
const isMobile = useSelector(selectIsMobile); const isMobile = useSelector(selectIsMobile);
const cardBg = PROFESSIONAL_COLORS.background.card; const cardBg = PROFESSIONAL_COLORS.background.card;
@@ -241,8 +245,8 @@ const StockListItem = ({
<MiniTimelineChart <MiniTimelineChart
stockCode={stock.stock_code} stockCode={stock.stock_code}
eventTime={eventTime} eventTime={eventTime}
preloadedData={klineData} preloadedData={timelineData}
loading={klineLoading} loading={timelineLoading}
/> />
</Box> </Box>
</VStack> </VStack>
@@ -285,6 +289,8 @@ const StockListItem = ({
<MiniKLineChart <MiniKLineChart
stockCode={stock.stock_code} stockCode={stock.stock_code}
eventTime={eventTime} eventTime={eventTime}
preloadedData={dailyData}
loading={dailyLoading}
/> />
</Box> </Box>
</VStack> </VStack>