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:
@@ -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')
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user