// 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.expectationScore - 超预期得分(0-100) * @param {'default'|'comfortable'|'large'} props.size - 尺寸模式:default=紧凑,comfortable=舒适(事件列表),large=大卡片(详情面板) */ const StockChangeIndicators = ({ avgChange, maxChange, expectationScore, 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 ( {/* Large 和 Default 模式:标签单独一行 */} {(isLarge || isDefault) && ( {label} )} {/* 数值 + 图标 */} {/* 三角形图标 */} {value !== 0 && ( value > 0 ? ( ) : ( ) )} {/* 数字 */} {/* Comfortable 模式:标签和数字在同一行 */} {!isLarge && !isDefault && ( {label}{' '} )} {sign}{numStr} % ); }; // 渲染超预期得分指标(特殊样式,分数而非百分比) const renderScoreIndicator = (label, score) => { if (score == null) return null; const labelColor = useColorModeValue('gray.600', 'gray.400'); // 根据分数确定颜色:>=60红色,>=40橙色,>=20蓝色,其他灰色 const getScoreColor = (s) => { if (s >= 60) return useColorModeValue('red.600', 'red.400'); if (s >= 40) return useColorModeValue('orange.600', 'orange.400'); if (s >= 20) return useColorModeValue('blue.600', 'blue.400'); return useColorModeValue('gray.600', 'gray.400'); }; const getScoreBgColor = (s) => { if (s >= 60) return useColorModeValue('red.50', 'red.900'); if (s >= 40) return useColorModeValue('orange.50', 'orange.900'); if (s >= 20) return useColorModeValue('blue.50', 'blue.900'); return useColorModeValue('gray.50', 'gray.800'); }; const getScoreBorderColor = (s) => { if (s >= 60) return useColorModeValue('red.200', 'red.700'); if (s >= 40) return useColorModeValue('orange.200', 'orange.700'); if (s >= 20) return useColorModeValue('blue.200', 'blue.700'); return useColorModeValue('gray.200', 'gray.700'); }; const scoreColor = getScoreColor(score); const bgColor = getScoreBgColor(score); const borderColor = getScoreBorderColor(score); return ( {/* Large 和 Default 模式:标签单独一行 */} {(isLarge || isDefault) && ( {label} )} {/* 数值 */} {/* Comfortable 模式:标签和数字在同一行 */} {!isLarge && !isDefault && ( {label} )} {Math.round(score)} ); }; // 如果没有任何数据,不渲染 if (avgChange == null && maxChange == null && expectationScore == null) { return null; } return ( {renderIndicator('平均超额', avgChange)} {renderIndicator('最大超额', maxChange)} {renderScoreIndicator('超预期', expectationScore)} ); }; export default StockChangeIndicators;