Files
vf_react/src/components/StockChangeIndicators.js
zdl b95607e9b4 refactor: 优化 StockChangeIndicators 颜色层次和视觉对比度
优化:
- 背景色统一使用 50 最浅色 (red.50/orange.50/green.50/teal.50)
- 边框色根据涨跌幅大小动态调整 (100-200 级别)
- 确保背景 < 边框 < 文字的颜色深度层次
- 提升视觉对比度和可读性
- 更新注释说明颜色逻辑

修改文件:
- src/components/StockChangeIndicators.js

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 16:01:42 +08:00

169 lines
6.7 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';
/**
* 股票涨跌幅指标组件3分天下布局
* @param {Object} props
* @param {number} props.avgChange - 平均涨跌幅
* @param {number} props.maxChange - 最大涨跌幅
* @param {number} props.weekChange - 周涨跌幅
*/
const StockChangeIndicators = ({
avgChange,
maxChange,
weekChange,
}) => {
// 根据涨跌幅获取数字颜色多颜色梯度5级分级
const getNumberColor = (value) => {
if (value == null) {
return useColorModeValue('gray.700', 'gray.400');
}
// 0值使用中性灰色
if (value === 0) {
return 'gray.700';
}
const absValue = Math.abs(value);
const isPositive = value > 0;
if (isPositive) {
// 上涨:红色系 → 橙色系
if (absValue >= 10) return 'red.900'; // 10%以上:最深红
if (absValue >= 5) return 'red.700'; // 5-10%:深红
if (absValue >= 3) return 'red.500'; // 3-5%:中红
if (absValue >= 1) return 'orange.600'; // 1-3%:橙色
return 'orange.400'; // 0-1%:浅橙
} else {
// 下跌:绿色系 → 青色系
if (absValue >= 10) return 'green.900'; // -10%以下:最深绿
if (absValue >= 5) return 'green.700'; // -10% ~ -5%:深绿
if (absValue >= 3) return 'green.500'; // -5% ~ -3%:中绿
if (absValue >= 1) return 'teal.600'; // -3% ~ -1%:青色
return 'teal.400'; // -1% ~ 0%:浅青
}
};
// 根据涨跌幅获取背景色(永远比文字色浅)
const getBgColor = (value) => {
if (value == null) {
return useColorModeValue('gray.50', 'gray.800');
}
// 0值使用中性灰色背景
if (value === 0) {
return useColorModeValue('gray.50', 'gray.800');
}
const absValue = Math.abs(value);
const isPositive = value > 0;
if (isPositive) {
// 上涨背景:红色系 → 橙色系(统一使用 50 最浅色)
if (absValue >= 10) return useColorModeValue('red.50', 'red.900');
if (absValue >= 5) return useColorModeValue('red.50', 'red.900');
if (absValue >= 3) return useColorModeValue('red.50', 'red.900');
if (absValue >= 1) return useColorModeValue('orange.50', 'orange.900');
return useColorModeValue('orange.50', 'orange.900');
} else {
// 下跌背景:绿色系 → 青色系(统一使用 50 最浅色)
if (absValue >= 10) return useColorModeValue('green.50', 'green.900');
if (absValue >= 5) return useColorModeValue('green.50', 'green.900');
if (absValue >= 3) return useColorModeValue('green.50', 'green.900');
if (absValue >= 1) return useColorModeValue('teal.50', 'teal.900');
return useColorModeValue('teal.50', 'teal.900');
}
};
// 根据涨跌幅获取边框色(比背景深,比文字浅)
const getBorderColor = (value) => {
if (value == null) {
return useColorModeValue('gray.200', 'gray.700');
}
// 0值使用中性灰色边框
if (value === 0) {
return useColorModeValue('gray.200', 'gray.700');
}
const absValue = Math.abs(value);
const isPositive = value > 0;
if (isPositive) {
// 上涨边框:红色系 → 橙色系(跟随文字深浅)
if (absValue >= 10) return useColorModeValue('red.200', 'red.800'); // 文字 red.900
if (absValue >= 5) return useColorModeValue('red.200', 'red.700'); // 文字 red.700
if (absValue >= 3) return useColorModeValue('red.100', 'red.600'); // 文字 red.500
if (absValue >= 1) return useColorModeValue('orange.200', 'orange.700'); // 文字 orange.600
return useColorModeValue('orange.100', 'orange.600'); // 文字 orange.400
} else {
// 下跌边框:绿色系 → 青色系(跟随文字深浅)
if (absValue >= 10) return useColorModeValue('green.200', 'green.800'); // 文字 green.900
if (absValue >= 5) return useColorModeValue('green.200', 'green.700'); // 文字 green.700
if (absValue >= 3) return useColorModeValue('green.100', 'green.600'); // 文字 green.500
if (absValue >= 1) return useColorModeValue('teal.200', 'teal.700'); // 文字 teal.600
return useColorModeValue('teal.100', 'teal.600'); // 文字 teal.400
}
};
// 渲染单个指标
const renderIndicator = (label, value) => {
if (value == null) return null;
const sign = value > 0 ? '+' : '';
// 0值显示为 "0",其他值显示一位小数
const numStr = value === 0 ? '0' : Math.abs(value).toFixed(1);
const numberColor = getNumberColor(value);
const bgColor = getBgColor(value);
const borderColor = getBorderColor(value);
const labelColor = useColorModeValue('gray.700', 'gray.400');
return (
<Box
bg={bgColor}
borderWidth="2px"
borderColor={borderColor}
borderRadius="md"
px={1.5}
py={0.5}
display="flex"
alignItems="center"
justifyContent="center"
>
<Text fontSize="xs" lineHeight="1.2">
<Text as="span" color={labelColor}>
{label}
</Text>
<Text as="span" color={labelColor}>
{sign}
</Text>
<Text as="span" fontWeight="bold" color={numberColor} fontSize="sm">
{value < 0 ? '-' : ''}{numStr}
</Text>
<Text as="span" color={labelColor}>
%
</Text>
</Text>
</Box>
);
};
// 如果没有任何数据,不渲染
if (avgChange == null && maxChange == null && weekChange == null) {
return null;
}
return (
<Flex width="100%" justify="space-between" align="center" gap={1}>
{renderIndicator('平均 ', avgChange)}
{renderIndicator('最大 ', maxChange)}
{renderIndicator('周涨 ', weekChange)}
</Flex>
);
};
export default StockChangeIndicators;