Files
vf_react/src/views/Company/components/FinancialPanorama/utils/calculations.ts
zdl c1394dbf19 refactor(StockCompareModal): 重构为 Ant Design 并统一主题配置
- 从 Chakra UI 迁移到 Ant Design (Modal, Table, Card)
- 新增 antdTheme.ts 统一 Ant Design 深色主题配置
- 提取 calculateDiff 到 FinancialPanorama/utils 复用
- 使用 useMemo 优化性能,提取子组件
- 添加独立的 .less 样式文件

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-26 13:59:44 +08:00

125 lines
3.5 KiB
TypeScript
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.

/**
* 财务计算工具函数
*/
/**
* 计算同比变化率
* @param currentValue 当前值
* @param currentPeriod 当前期间
* @param allData 所有数据
* @param metricPath 指标路径
* @returns 变化率和强度
*/
export const calculateYoYChange = (
currentValue: number | null | undefined,
currentPeriod: string,
allData: Array<{ period: string; [key: string]: unknown }>,
metricPath: string
): { change: number; intensity: number } => {
if (!currentValue || !currentPeriod) return { change: 0, intensity: 0 };
// 找到去年同期的数据
const currentDate = new Date(currentPeriod);
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth() + 1;
// 查找去年同期
const lastYearSamePeriod = allData.find((item) => {
const itemDate = new Date(item.period);
const itemYear = itemDate.getFullYear();
const itemMonth = itemDate.getMonth() + 1;
return itemYear === currentYear - 1 && itemMonth === currentMonth;
});
if (!lastYearSamePeriod) return { change: 0, intensity: 0 };
const previousValue = metricPath
.split('.')
.reduce((obj: unknown, key: string) => {
if (obj && typeof obj === 'object') {
return (obj as Record<string, unknown>)[key];
}
return undefined;
}, lastYearSamePeriod) as number | undefined;
if (!previousValue || previousValue === 0) return { change: 0, intensity: 0 };
const change = ((currentValue - previousValue) / Math.abs(previousValue)) * 100;
const intensity = Math.min(Math.abs(change) / 50, 1); // 50%变化达到最大强度
return { change, intensity };
};
/**
* 获取单元格背景色(中国市场颜色)
* @param change 变化率
* @param intensity 强度
* @returns 背景色
*/
export const getCellBackground = (change: number, intensity: number): string => {
if (change > 0) {
return `rgba(239, 68, 68, ${intensity * 0.15})`; // 红色背景,涨
} else if (change < 0) {
return `rgba(34, 197, 94, ${intensity * 0.15})`; // 绿色背景,跌
}
return 'transparent';
};
/**
* 从对象中获取嵌套路径的值
* @param obj 对象
* @param path 路径(如 'assets.current_assets.cash'
* @returns 值
*/
export const getValueByPath = <T = unknown>(
obj: unknown,
path: string
): T | undefined => {
return path.split('.').reduce((current: unknown, key: string) => {
if (current && typeof current === 'object') {
return (current as Record<string, unknown>)[key];
}
return undefined;
}, obj) as T | undefined;
};
/**
* 判断是否为成本费用类指标(负向指标)
* @param key 指标 key
* @returns 是否为负向指标
*/
export const isNegativeIndicator = (key: string): boolean => {
return (
key.includes('cost') ||
key.includes('expense') ||
key === 'income_tax' ||
key.includes('impairment') ||
key.includes('days') ||
key.includes('debt_ratio')
);
};
/**
* 计算两个值的差异百分比
* @param value1 当前股票值
* @param value2 对比股票值
* @param format 格式类型percent 直接相减number 计算变化率
* @returns 差异百分比或 null
*/
export const calculateDiff = (
value1: number | null | undefined,
value2: number | null | undefined,
format: 'percent' | 'number'
): number | null => {
if (value1 == null || value2 == null) return null;
if (format === 'percent') {
return value1 - value2;
}
if (value2 !== 0) {
return ((value1 - value2) / Math.abs(value2)) * 100;
}
return null;
};