- 从 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>
125 lines
3.5 KiB
TypeScript
125 lines
3.5 KiB
TypeScript
/**
|
||
* 财务计算工具函数
|
||
*/
|
||
|
||
/**
|
||
* 计算同比变化率
|
||
* @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;
|
||
};
|