Files
vf_react/src/components/StockChangeIndicators.js
2025-11-07 10:56:08 +08:00

164 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/components/StockChangeIndicators.js
// 股票涨跌幅指标组件(通用)
import React from 'react';
import { Flex, Box, Text, useColorModeValue } from '@chakra-ui/react';
import { TriangleUpIcon, TriangleDownIcon } from '@chakra-ui/icons';
import { getChangeColor } from '../utils/colorUtils';
/**
* 股票涨跌幅指标组件3分天下布局
* @param {Object} props
* @param {number} props.avgChange - 平均涨跌幅
* @param {number} props.maxChange - 最大涨跌幅
* @param {number} props.weekChange - 周涨跌幅
* @param {'default'|'comfortable'|'large'} props.size - 尺寸模式default=紧凑comfortable=舒适事件列表large=大卡片(详情面板)
*/
const StockChangeIndicators = ({
avgChange,
maxChange,
weekChange,
size = 'default',
}) => {
const isLarge = size === 'large';
const isComfortable = size === 'comfortable';
const isDefault = size === 'default';
// 根据涨跌幅获取数字颜色(动态深浅)
const getNumberColor = (value) => {
if (value == null) {
return useColorModeValue('gray.700', 'gray.400');
}
// 使用动态颜色函数
return getChangeColor(value);
};
// 根据涨跌幅获取背景色(永远比文字色浅)
const getBgColor = (value) => {
if (value == null) {
return useColorModeValue('gray.50', 'gray.800');
}
// 0值使用中性灰色背景
if (value === 0) {
return useColorModeValue('gray.50', 'gray.800');
}
// 统一背景色:上涨红色系,下跌绿色系
return value > 0
? useColorModeValue('red.50', 'red.900')
: useColorModeValue('green.50', 'green.900');
};
// 根据涨跌幅获取边框色(比背景深,比文字浅)
const getBorderColor = (value) => {
if (value == null) {
return useColorModeValue('gray.200', 'gray.700');
}
// 0值使用中性灰色边框
if (value === 0) {
return useColorModeValue('gray.200', 'gray.700');
}
// 统一边框色:上涨红色系,下跌绿色系
return value > 0
? useColorModeValue('red.200', 'red.700')
: useColorModeValue('green.200', 'green.700');
};
// 渲染单个指标
const renderIndicator = (label, value) => {
if (value == null) return null;
const sign = value > 0 ? '+' : '-';
// 0值显示为 "0",其他值显示两位小数
const numStr = value === 0 ? '0' : Math.abs(value).toFixed(2);
const numberColor = getNumberColor(value);
const bgColor = getBgColor(value);
const borderColor = getBorderColor(value);
const labelColor = useColorModeValue('gray.600', 'gray.400');
return (
<Box
bg={bgColor}
borderWidth={isLarge ? "2px" : "1px"}
borderColor={borderColor}
borderRadius="md"
px={isLarge ? 4 : (isDefault ? 1.5 : (isComfortable ? 3 : 2))}
py={isLarge ? 3 : (isDefault ? 1.5 : (isComfortable ? 2 : 1))}
display="flex"
flexDirection={(isLarge || isDefault) ? "column" : "row"}
alignItems={(isLarge || isDefault) ? "flex-start" : "center"}
gap={(isLarge || isDefault) ? (isLarge ? 2 : 1) : 1}
maxW={isLarge ? "200px" : "none"}
flex="0 1 auto"
minW="0"
>
{/* Large 和 Default 模式:标签单独一行 */}
{(isLarge || isDefault) && (
<Text fontSize={isLarge ? "sm" : "xs"} color={labelColor} fontWeight="medium">
{label.trim()}
</Text>
)}
{/* 数值 + 图标 */}
<Flex align="center" gap={isLarge ? 2 : (isDefault ? 1 : 1)}>
{/* 三角形图标 */}
{value !== 0 && (
value > 0 ? (
<TriangleUpIcon
w={2}
h={2}
color={numberColor}
/>
) : (
<TriangleDownIcon
w={2}
h={2}
color={numberColor}
/>
)
)}
{/* 数字 */}
<Text
fontSize={isLarge ? "2xl" : (isDefault ? "md" : "lg")}
fontWeight="bold"
color={numberColor}
lineHeight="1.2"
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
>
{/* Comfortable 模式:标签和数字在同一行 */}
{!isLarge && !isDefault && (
<Text as="span" color={labelColor} fontWeight="medium" fontSize="sm">
{label}
</Text>
)}
{sign}{numStr}
<Text as="span" fontWeight="medium" fontSize="sm">%</Text>
</Text>
</Flex>
</Box>
);
};
// 如果没有任何数据,不渲染
if (avgChange == null && maxChange == null && weekChange == null) {
return null;
}
return (
<Flex width="100%" justify="flex-start" align="center" gap={isLarge ? 4 : (isDefault ? 2 : 1)}>
{renderIndicator('平均涨幅', avgChange)}
{renderIndicator('最大涨幅', maxChange)}
{renderIndicator('周涨幅', weekChange)}
</Flex>
);
};
export default StockChangeIndicators;