164 lines
5.9 KiB
JavaScript
164 lines
5.9 KiB
JavaScript
// 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;
|