update pay ui
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user