update pay ui
This commit is contained in:
@@ -1,13 +1,13 @@
|
|||||||
// src/components/StockChangeIndicators.js
|
// src/components/StockChangeIndicators.js
|
||||||
// 股票涨跌幅指标组件(通用)
|
// 股票涨跌幅指标组件(通用)
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Flex, Box, Text, useColorModeValue } from '@chakra-ui/react';
|
import { Flex, Box, Text, useColorModeValue } from '@chakra-ui/react';
|
||||||
import { TriangleUpIcon, TriangleDownIcon } from '@chakra-ui/icons';
|
import { TriangleUpIcon, TriangleDownIcon } from '@chakra-ui/icons';
|
||||||
import { getChangeColor } from '../utils/colorUtils';
|
import { getChangeColor } from '../utils/colorUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 股票涨跌幅指标组件(3分天下布局)
|
* 股票涨跌幅指标组件(2个指标:超额涨幅可切换 + 超预期得分)
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
* @param {number} props.avgChange - 平均超额涨幅
|
* @param {number} props.avgChange - 平均超额涨幅
|
||||||
* @param {number} props.maxChange - 最大超额涨幅
|
* @param {number} props.maxChange - 最大超额涨幅
|
||||||
@@ -20,6 +20,9 @@ const StockChangeIndicators = ({
|
|||||||
expectationScore,
|
expectationScore,
|
||||||
size = 'default',
|
size = 'default',
|
||||||
}) => {
|
}) => {
|
||||||
|
// 点击切换显示最大超额/平均超额
|
||||||
|
const [showMax, setShowMax] = useState(true);
|
||||||
|
|
||||||
const isLarge = size === 'large';
|
const isLarge = size === 'large';
|
||||||
const isComfortable = size === 'comfortable';
|
const isComfortable = size === 'comfortable';
|
||||||
const isDefault = size === 'default';
|
const isDefault = size === 'default';
|
||||||
@@ -68,8 +71,11 @@ const StockChangeIndicators = ({
|
|||||||
: useColorModeValue('green.200', 'green.700');
|
: useColorModeValue('green.200', 'green.700');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 渲染单个指标
|
// 渲染可切换的超额指标(最大超额/平均超额)
|
||||||
const renderIndicator = (label, value) => {
|
const renderToggleIndicator = () => {
|
||||||
|
const value = showMax ? maxChange : avgChange;
|
||||||
|
const label = showMax ? '最大超额' : '平均超额';
|
||||||
|
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
|
|
||||||
const sign = value > 0 ? '+' : '-';
|
const sign = value > 0 ? '+' : '-';
|
||||||
@@ -95,11 +101,18 @@ const StockChangeIndicators = ({
|
|||||||
maxW={isLarge ? "200px" : "none"}
|
maxW={isLarge ? "200px" : "none"}
|
||||||
flex="0 1 auto"
|
flex="0 1 auto"
|
||||||
minW="0"
|
minW="0"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowMax(!showMax);
|
||||||
|
}}
|
||||||
|
_hover={{ opacity: 0.85 }}
|
||||||
|
title={`点击切换${showMax ? '平均超额' : '最大超额'}`}
|
||||||
>
|
>
|
||||||
{/* Large 和 Default 模式:标签单独一行 */}
|
{/* Large 和 Default 模式:标签单独一行 */}
|
||||||
{(isLarge || isDefault) && (
|
{(isLarge || isDefault) && (
|
||||||
<Text fontSize={isLarge ? "sm" : "xs"} color={labelColor} fontWeight="medium">
|
<Text fontSize={isLarge ? "sm" : "xs"} color={labelColor} fontWeight="medium">
|
||||||
{label.trim()}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -135,7 +148,7 @@ const StockChangeIndicators = ({
|
|||||||
{/* Comfortable 模式:标签和数字在同一行 */}
|
{/* Comfortable 模式:标签和数字在同一行 */}
|
||||||
{!isLarge && !isDefault && (
|
{!isLarge && !isDefault && (
|
||||||
<Text as="span" color={labelColor} fontWeight="medium" fontSize="sm">
|
<Text as="span" color={labelColor} fontWeight="medium" fontSize="sm">
|
||||||
{label}
|
{label}{' '}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{sign}{numStr}
|
{sign}{numStr}
|
||||||
@@ -229,8 +242,9 @@ const StockChangeIndicators = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex width="100%" justify="flex-start" align="center" gap={isLarge ? 4 : (isDefault ? 2 : 1)}>
|
<Flex width="100%" justify="flex-start" align="center" gap={isLarge ? 4 : (isDefault ? 2 : 1)}>
|
||||||
{renderIndicator('最大超额', maxChange)}
|
{/* 可切换的超额指标(最大超额/平均超额) */}
|
||||||
{renderIndicator('平均超额', avgChange)}
|
{renderToggleIndicator()}
|
||||||
|
{/* 超预期得分 */}
|
||||||
{renderScoreIndicator('超预期', expectationScore)}
|
{renderScoreIndicator('超预期', expectationScore)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,19 +40,19 @@ const MiniTimelineChart = React.memo(function MiniTimelineChart({ stockCode, eve
|
|||||||
|
|
||||||
// 从缓存或API获取数据的函数
|
// 从缓存或API获取数据的函数
|
||||||
const loadData = useCallback(() => {
|
const loadData = useCallback(() => {
|
||||||
if (!stockCode || !mountedRef.current) return;
|
if (!stockCode || !mountedRef.current) return false;
|
||||||
|
|
||||||
// 检查缓存
|
// 检查缓存
|
||||||
const cacheKey = getCacheKey(stockCode, stableEventTime);
|
const cacheKey = getCacheKey(stockCode, stableEventTime);
|
||||||
const cachedData = klineDataCache.get(cacheKey);
|
const cachedData = klineDataCache.get(cacheKey);
|
||||||
|
|
||||||
// 如果有缓存数据,直接使用
|
// 如果有缓存数据(包括空数组,表示已请求过但无数据),直接使用
|
||||||
if (cachedData && cachedData.length > 0) {
|
if (cachedData !== undefined) {
|
||||||
setData(cachedData);
|
setData(cachedData || []);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
loadedRef.current = true;
|
loadedRef.current = true;
|
||||||
dataFetchedRef.current = true;
|
dataFetchedRef.current = true;
|
||||||
return true; // 表示数据已加载
|
return true; // 表示数据已加载(或确认无数据)
|
||||||
}
|
}
|
||||||
return false; // 表示需要请求
|
return false; // 表示需要请求
|
||||||
}, [stockCode, stableEventTime]);
|
}, [stockCode, stableEventTime]);
|
||||||
@@ -75,48 +75,81 @@ const MiniTimelineChart = React.memo(function MiniTimelineChart({ stockCode, eve
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有正在进行的批量请求
|
// 检查批量请求的函数
|
||||||
const batchKey = `${stableEventTime || 'today'}|timeline`;
|
const checkBatchAndLoad = () => {
|
||||||
const pendingBatch = batchPendingRequests.get(batchKey);
|
// 再次检查缓存(批量请求可能已完成)
|
||||||
|
const cacheKey = getCacheKey(stockCode, stableEventTime);
|
||||||
|
const cachedData = klineDataCache.get(cacheKey);
|
||||||
|
if (cachedData !== undefined) {
|
||||||
|
setData(cachedData || []);
|
||||||
|
setLoading(false);
|
||||||
|
loadedRef.current = true;
|
||||||
|
dataFetchedRef.current = true;
|
||||||
|
return true; // 从缓存加载成功
|
||||||
|
}
|
||||||
|
|
||||||
if (pendingBatch) {
|
const batchKey = `${stableEventTime || 'today'}|timeline`;
|
||||||
// 等待批量请求完成后再从缓存读取
|
const pendingBatch = batchPendingRequests.get(batchKey);
|
||||||
setLoading(true);
|
|
||||||
dataFetchedRef.current = true;
|
if (pendingBatch) {
|
||||||
pendingBatch.then(() => {
|
// 等待批量请求完成后再从缓存读取
|
||||||
if (mountedRef.current) {
|
setLoading(true);
|
||||||
loadData();
|
dataFetchedRef.current = true;
|
||||||
setLoading(false);
|
pendingBatch.then(() => {
|
||||||
}
|
if (mountedRef.current) {
|
||||||
}).catch(() => {
|
const newCachedData = klineDataCache.get(cacheKey);
|
||||||
if (mountedRef.current) {
|
setData(newCachedData || []);
|
||||||
setData([]);
|
setLoading(false);
|
||||||
setLoading(false);
|
loadedRef.current = true;
|
||||||
}
|
}
|
||||||
});
|
}).catch(() => {
|
||||||
|
if (mountedRef.current) {
|
||||||
|
setData([]);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true; // 找到批量请求
|
||||||
|
}
|
||||||
|
return false; // 没有批量请求
|
||||||
|
};
|
||||||
|
|
||||||
|
// 先立即检查一次
|
||||||
|
if (checkBatchAndLoad()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有批量请求,发起单独请求
|
// 延迟一小段时间再检查(等待批量请求启动)
|
||||||
dataFetchedRef.current = true;
|
// 因为 StockTable 的 useEffect 可能还没执行
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
if (!mountedRef.current || dataFetchedRef.current) return;
|
||||||
|
|
||||||
// 使用全局的fetchKlineData函数
|
// 再次检查批量请求
|
||||||
fetchKlineData(stockCode, stableEventTime)
|
if (checkBatchAndLoad()) {
|
||||||
.then((result) => {
|
return;
|
||||||
if (mountedRef.current) {
|
}
|
||||||
setData(result);
|
|
||||||
setLoading(false);
|
// 仍然没有批量请求,发起单独请求
|
||||||
loadedRef.current = true;
|
dataFetchedRef.current = true;
|
||||||
}
|
|
||||||
})
|
fetchKlineData(stockCode, stableEventTime)
|
||||||
.catch(() => {
|
.then((result) => {
|
||||||
if (mountedRef.current) {
|
if (mountedRef.current) {
|
||||||
setData([]);
|
setData(result);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
loadedRef.current = true;
|
loadedRef.current = true;
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (mountedRef.current) {
|
||||||
|
setData([]);
|
||||||
|
setLoading(false);
|
||||||
|
loadedRef.current = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 50); // 延迟 50ms 等待批量请求启动
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
}, [stockCode, stableEventTime, loadData]); // 注意这里使用 stableEventTime
|
}, [stockCode, stableEventTime, loadData]); // 注意这里使用 stableEventTime
|
||||||
|
|
||||||
const chartOption = useMemo(() => {
|
const chartOption = useMemo(() => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// src/views/Community/components/StockDetailPanel/components/StockTable.js
|
// src/views/Community/components/StockDetailPanel/components/StockTable.js
|
||||||
import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
||||||
import { Table, Button } from 'antd';
|
import { Table, Button } from 'antd';
|
||||||
import { StarFilled, StarOutlined } from '@ant-design/icons';
|
import { StarFilled, StarOutlined } from '@ant-design/icons';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@@ -29,7 +29,6 @@ const StockTable = ({
|
|||||||
}) => {
|
}) => {
|
||||||
// 展开/收缩的行
|
// 展开/收缩的行
|
||||||
const [expandedRows, setExpandedRows] = useState(new Set());
|
const [expandedRows, setExpandedRows] = useState(new Set());
|
||||||
const preloadedRef = useRef(false); // 标记是否已预加载
|
|
||||||
|
|
||||||
// 稳定的事件时间,避免重复渲染
|
// 稳定的事件时间,避免重复渲染
|
||||||
const stableEventTime = useMemo(() => {
|
const stableEventTime = useMemo(() => {
|
||||||
@@ -37,22 +36,21 @@ const StockTable = ({
|
|||||||
}, [eventTime]);
|
}, [eventTime]);
|
||||||
|
|
||||||
// 批量预加载K线数据
|
// 批量预加载K线数据
|
||||||
|
// 使用 stocks 的 JSON 字符串作为依赖项的 key,避免引用变化导致重复预加载
|
||||||
|
const stocksKey = useMemo(() => {
|
||||||
|
return stocks.map(s => s.stock_code).sort().join(',');
|
||||||
|
}, [stocks]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stocks.length > 0 && !preloadedRef.current) {
|
if (stocks.length > 0) {
|
||||||
const stockCodes = stocks.map(s => s.stock_code);
|
const stockCodes = stocks.map(s => s.stock_code);
|
||||||
logger.debug('StockTable', '批量预加载K线数据', {
|
logger.debug('StockTable', '批量预加载K线数据', {
|
||||||
stockCount: stockCodes.length,
|
stockCount: stockCodes.length,
|
||||||
eventTime: stableEventTime
|
eventTime: stableEventTime
|
||||||
});
|
});
|
||||||
preloadBatchKlineData(stockCodes, stableEventTime, 'timeline');
|
preloadBatchKlineData(stockCodes, stableEventTime, 'timeline');
|
||||||
preloadedRef.current = true;
|
|
||||||
}
|
}
|
||||||
}, [stocks, stableEventTime]);
|
}, [stocksKey, stableEventTime]); // 使用 stocksKey 而非 stocks 对象引用
|
||||||
|
|
||||||
// 当股票列表变化时重置预加载标记
|
|
||||||
useEffect(() => {
|
|
||||||
preloadedRef.current = false;
|
|
||||||
}, [stocks.length]);
|
|
||||||
|
|
||||||
// 切换行展开状态
|
// 切换行展开状态
|
||||||
const toggleRowExpand = useCallback((stockCode) => {
|
const toggleRowExpand = useCallback((stockCode) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user