增加主力数据

This commit is contained in:
2025-12-29 16:17:23 +08:00
parent a9cb60a12b
commit 0e58af9f94
6 changed files with 123 additions and 69 deletions

View File

@@ -1,28 +1,25 @@
/**
* MainForceInfo - 主力动态原子组件
*
* 显示主力资金和机构相关指标
* - 主力净流入(带正负颜色)
* - 机构持仓比例
* - 买卖比例进度条
* 显示主力资金流向指标数据来源stock_main_capital_flow 表)
* - 主力净流入(万元,带正负颜色)
* - 主力流入占比(%
* - 净主动买入占比(%,进度条展示)
*
* 注意:标题由外层 GlassSection 提供
*/
import React, { memo } from 'react';
import { Box, VStack, HStack, Text, Progress } from '@chakra-ui/react';
import { formatNetInflow } from './formatters';
import { DEEP_SPACE_THEME as T } from './theme';
export interface MainForceInfoProps {
/** 主力净流入(亿 */
mainNetInflow: number;
/** 机构持仓比例% */
institutionHolding: number;
/** 买入比例% */
buyRatio: number;
/** 卖出比例(% */
sellRatio: number;
/** 主力净流入(万元 */
netInflow: number | null;
/** 主力净流入量占比% */
mainInflowRatio: number | null;
/** 净主动买入额占比% */
netActiveBuyRatio: number | null;
}
/**
@@ -54,6 +51,32 @@ const MetricRow: React.FC<MetricRowProps> = ({
</HStack>
);
/**
* 格式化主力净流入显示
* 根据数值大小自动选择万/亿单位
*/
const formatNetInflowValue = (value: number | null): string => {
if (value === null || value === undefined) return '--';
const absValue = Math.abs(value);
const sign = value >= 0 ? '+' : '';
if (absValue >= 10000) {
// 超过1亿显示为亿
return `${sign}${(value / 10000).toFixed(2)}亿`;
}
// 否则显示万
return `${sign}${value.toFixed(2)}`;
};
/**
* 格式化百分比显示
*/
const formatPercent = (value: number | null): string => {
if (value === null || value === undefined) return '--';
const sign = value >= 0 ? '+' : '';
return `${sign}${value.toFixed(2)}%`;
};
/**
* 主力动态展示组件
*
@@ -61,51 +84,75 @@ const MetricRow: React.FC<MetricRowProps> = ({
* 应由 GlassSection 包装以提供标题
*/
export const MainForceInfo: React.FC<MainForceInfoProps> = memo(({
mainNetInflow,
institutionHolding,
buyRatio,
sellRatio,
netInflow,
mainInflowRatio,
netActiveBuyRatio,
}) => {
const inflowColor = mainNetInflow >= 0 ? T.upColor : T.downColor;
const inflowColor = (netInflow ?? 0) >= 0 ? T.upColor : T.downColor;
const ratioColor = (mainInflowRatio ?? 0) >= 0 ? T.upColor : T.downColor;
// 净主动买入占比用于进度条,范围可能是 -100 ~ 100需要转换
// 负值表示主动卖出多,正值表示主动买入多
const buyRatioValue = netActiveBuyRatio ?? 0;
// 转换为 0-100 的进度条值50为中点
const progressValue = Math.min(100, Math.max(0, 50 + buyRatioValue / 2));
return (
<VStack align="stretch" spacing={2}>
<MetricRow
label="主力净流入"
value={formatNetInflow(mainNetInflow)}
value={formatNetInflowValue(netInflow)}
valueColor={inflowColor}
highlight
/>
<MetricRow
label="机构持仓"
value={`${institutionHolding.toFixed(2)}%`}
valueColor={T.purple}
label="流入占比"
value={formatPercent(mainInflowRatio)}
valueColor={ratioColor}
highlight
/>
{/* 买卖比例进度条 */}
{/* 净主动买入占比进度条 */}
<Box mt={2}>
<Progress
value={buyRatio}
size="sm"
sx={{
'& > div': {
bg: T.upColor,
boxShadow: T.upGlow,
},
}}
bg={T.downColor}
borderRadius="full"
h="8px"
/>
<HStack justify="space-between" mt={2} fontSize="13px">
<Text color={T.upColor} fontWeight="600">
{buyRatio}%
</Text>
<Text color={T.downColor} fontWeight="600">
{sellRatio}%
<HStack justify="space-between" mb={1} fontSize="12px">
<Text color={T.textMuted}></Text>
<Text
color={buyRatioValue >= 0 ? T.upColor : T.downColor}
fontWeight="600"
>
{formatPercent(netActiveBuyRatio)}
</Text>
</HStack>
<Box position="relative">
<Progress
value={progressValue}
size="sm"
sx={{
'& > div': {
bg: buyRatioValue >= 0 ? T.upColor : T.downColor,
boxShadow: buyRatioValue >= 0 ? T.upGlow : T.downGlow,
transition: 'all 0.3s ease',
},
}}
bg="rgba(255,255,255,0.1)"
borderRadius="full"
h="8px"
/>
{/* 中点标记 */}
<Box
position="absolute"
left="50%"
top="0"
bottom="0"
w="2px"
bg="rgba(255,255,255,0.3)"
transform="translateX(-50%)"
/>
</Box>
<HStack justify="space-between" mt={1} fontSize="11px">
<Text color={T.downColor}></Text>
<Text color={T.upColor}></Text>
</HStack>
</Box>
</VStack>
);

View File

@@ -19,11 +19,3 @@ export const formatChangePercent = (percent: number): string => {
const sign = percent >= 0 ? '+' : '';
return `${sign}${percent.toFixed(2)}%`;
};
/**
* 格式化主力净流入显示
*/
export const formatNetInflow = (value: number): string => {
const sign = value >= 0 ? '+' : '';
return `${sign}${value.toFixed(2)}亿`;
};

View File

@@ -42,11 +42,10 @@ const transformQuoteData = (apiData: any, stockCode: string): StockQuoteCardData
week52Low: apiData.week52_low || apiData.week52Low || 0,
week52High: apiData.week52_high || apiData.week52High || 0,
// 主力动态
mainNetInflow: apiData.main_net_inflow || apiData.mainNetInflow || 0,
institutionHolding: apiData.institution_holding || apiData.institutionHolding || 0,
buyRatio: apiData.buy_ratio || apiData.buyRatio || 50,
sellRatio: apiData.sell_ratio || apiData.sellRatio || 50,
// 主力动态数据来源stock_main_capital_flow 表)
netInflow: apiData.net_inflow ?? apiData.netInflow ?? null, // 主力净流入量(万元)
mainInflowRatio: apiData.main_inflow_ratio ?? apiData.mainInflowRatio ?? null, // 主力净流入量占比(%
netActiveBuyRatio: apiData.net_active_buy_ratio ?? apiData.netActiveBuyRatio ?? null, // 净主动买入额占比(%
// 更新时间
updateTime: apiData.update_time || apiData.updateTime || new Date().toLocaleString(),

View File

@@ -179,10 +179,9 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
{/* 第三列:主力动态 */}
<GlassSection title="主力动态" flex={1}>
<MainForceInfo
mainNetInflow={quoteData.mainNetInflow || 0}
institutionHolding={quoteData.institutionHolding}
buyRatio={quoteData.buyRatio}
sellRatio={quoteData.sellRatio}
netInflow={quoteData.netInflow}
mainInflowRatio={quoteData.mainInflowRatio}
netActiveBuyRatio={quoteData.netActiveBuyRatio}
/>
</GlassSection>
</Flex>

View File

@@ -33,11 +33,10 @@ export interface StockQuoteCardData {
week52Low: number; // 52周最低
week52High: number; // 52周最高
// 主力动态
mainNetInflow: number; // 主力净流入(亿
institutionHolding: number; // 机构持仓比例(百分比
buyRatio: number; // 买入比例(百分比
sellRatio: number; // 卖出比例(百分比)
// 主力动态数据来源stock_main_capital_flow 表)
netInflow: number | null; // 主力净流入量(万元
mainInflowRatio: number | null; // 主力净流入量占比(%
netActiveBuyRatio: number | null; // 净主动买入额占比(%
// 更新时间
updateTime: string; // 格式YYYY-MM-DD HH:mm:ss