refactor(FinancialPanorama): 添加工具函数模块

计算工具 (calculations.ts):
- calculateYoYChange: 同比变化率计算
- getCellBackground: 单元格背景色(红涨绿跌)
- getValueByPath: 嵌套路径取值
- isNegativeIndicator: 负向指标判断

图表配置 (chartOptions.ts):
- getMetricChartOption: 指标趋势柱状图
- getComparisonChartOption: 营收利润双轴图
- getMainBusinessPieOption: 主营业务饼图
- getCompareBarChartOption: 股票对比柱状图

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-12 15:01:09 +08:00
parent a424b3338d
commit fb42ef566b
3 changed files with 366 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
/**
* 财务计算工具函数
*/
/**
* 计算同比变化率
* @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')
);
};