// src/services/financialService.js /** * 完整的财务数据服务层 * 对应Flask后端的所有财务API接口 */ import axios from '@utils/axiosConfig'; /** * 统一的 API 请求函数 * axios 拦截器已自动处理日志记录 * @param {string} url - 请求 URL * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号,用于取消请求 */ const apiRequest = async (url, options = {}) => { const { method = 'GET', body, signal, ...rest } = options; const config = { method, url, signal, ...rest, }; // 如果有 body,根据方法设置 data 或 params if (body) { if (method === 'GET') { config.params = typeof body === 'string' ? JSON.parse(body) : body; } else { config.data = typeof body === 'string' ? JSON.parse(body) : body; } } const response = await axios(config); return response.data; }; export const financialService = { /** * 获取股票基本信息和最新财务摘要 * @param {string} seccode - 股票代码 * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号 */ getStockInfo: async (seccode, options = {}) => { return await apiRequest(`/api/financial/stock-info/${seccode}`, options); }, /** * 获取完整的资产负债表数据 * @param {string} seccode - 股票代码 * @param {number} limit - 获取的报告期数量 * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号 */ getBalanceSheet: async (seccode, limit = 12, options = {}) => { return await apiRequest(`/api/financial/balance-sheet/${seccode}?limit=${limit}`, options); }, /** * 获取完整的利润表数据 * @param {string} seccode - 股票代码 * @param {number} limit - 获取的报告期数量 * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号 */ getIncomeStatement: async (seccode, limit = 12, options = {}) => { return await apiRequest(`/api/financial/income-statement/${seccode}?limit=${limit}`, options); }, /** * 获取完整的现金流量表数据 * @param {string} seccode - 股票代码 * @param {number} limit - 获取的报告期数量 * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号 */ getCashflow: async (seccode, limit = 12, options = {}) => { return await apiRequest(`/api/financial/cashflow/${seccode}?limit=${limit}`, options); }, /** * 获取完整的财务指标数据 * @param {string} seccode - 股票代码 * @param {number} limit - 获取的报告期数量 * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号 */ getFinancialMetrics: async (seccode, limit = 12, options = {}) => { return await apiRequest(`/api/financial/financial-metrics/${seccode}?limit=${limit}`, options); }, /** * 获取主营业务构成数据 * @param {string} seccode - 股票代码 * @param {number} periods - 获取的报告期数量 * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号 */ getMainBusiness: async (seccode, periods = 4, options = {}) => { return await apiRequest(`/api/financial/main-business/${seccode}?periods=${periods}`, options); }, /** * 获取业绩预告和预披露时间 * @param {string} seccode - 股票代码 * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号 */ getForecast: async (seccode, options = {}) => { return await apiRequest(`/api/financial/forecast/${seccode}`, options); }, /** * 获取行业排名数据 * @param {string} seccode - 股票代码 * @param {number} limit - 获取的报告期数量 * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号 */ getIndustryRank: async (seccode, limit = 4, options = {}) => { return await apiRequest(`/api/financial/industry-rank/${seccode}?limit=${limit}`, options); }, /** * 获取不同报告期的对比数据 * @param {string} seccode - 股票代码 * @param {number} periods - 对比的报告期数量 * @param {object} options - 请求选项 * @param {AbortSignal} options.signal - AbortController 信号 */ getPeriodComparison: async (seccode, periods = 8, options = {}) => { return await apiRequest(`/api/financial/comparison/${seccode}?periods=${periods}`, options); }, }; // 数据格式化工具函数 export const formatUtils = { /** * 格式化大数字(亿/万) * @param {number} num - 数字 * @param {number} decimal - 小数位数 */ formatLargeNumber: (num, decimal = 2) => { if (num === null || num === undefined) return '-'; const absNum = Math.abs(num); let result = ''; if (absNum >= 100000000) { result = (num / 100000000).toFixed(decimal) + '亿'; } else if (absNum >= 10000) { result = (num / 10000).toFixed(decimal) + '万'; } else if (absNum >= 1) { result = num.toFixed(decimal); } else { result = num.toFixed(4); // 小于1的数字显示更多小数位 } return result; }, /** * 格式化百分比 * @param {number} num - 数字 * @param {number} decimal - 小数位数 */ formatPercent: (num, decimal = 2) => { if (num === null || num === undefined) return '-'; return num.toFixed(decimal) + '%'; }, /** * 格式化日期 * @param {string} dateStr - 日期字符串 */ formatDate: (dateStr) => { if (!dateStr) return '-'; const date = new Date(dateStr); return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; }, /** * 获取报告期类型 * @param {string} dateStr - 日期字符串 */ getReportType: (dateStr) => { if (!dateStr) return ''; const date = new Date(dateStr); const month = date.getMonth() + 1; const year = date.getFullYear(); switch(month) { case 3: return `${year}Q1`; case 6: return `${year}中报`; case 9: return `${year}Q3`; case 12: return `${year}年报`; default: return dateStr; } }, /** * 格式化增长率颜色(用于Chakra UI) * @param {number} value - 增长率数值 */ getGrowthColor: (value) => { if (value === null || value === undefined) return 'gray.500'; if (value > 0) return 'green.500'; if (value < 0) return 'red.500'; return 'gray.500'; }, /** * 获取指标变化趋势图标 * @param {number} current - 当前值 * @param {number} previous - 之前的值 */ getTrendIcon: (current, previous) => { if (!current || !previous) return 'stable'; if (current > previous) return 'up'; if (current < previous) return 'down'; return 'stable'; }, /** * 计算同比增长率 * @param {number} current - 当前值 * @param {number} yearAgo - 去年同期值 */ calculateYoY: (current, yearAgo) => { if (!current || !yearAgo) return null; return ((current - yearAgo) / Math.abs(yearAgo)) * 100; }, /** * 计算环比增长率 * @param {number} current - 当前值 * @param {number} previous - 上期值 */ calculateQoQ: (current, previous) => { if (!current || !previous) return null; return ((current - previous) / Math.abs(previous)) * 100; }, /** * 获取财务健康度评分 * @param {object} metrics - 财务指标对象 */ getFinancialHealthScore: (metrics) => { let score = 0; let factors = 0; // ROE评分 if (metrics.roe !== null && metrics.roe !== undefined) { factors++; if (metrics.roe > 20) score += 5; else if (metrics.roe > 15) score += 4; else if (metrics.roe > 10) score += 3; else if (metrics.roe > 5) score += 2; else if (metrics.roe > 0) score += 1; } // 流动比率评分 if (metrics.current_ratio !== null && metrics.current_ratio !== undefined) { factors++; if (metrics.current_ratio > 2) score += 5; else if (metrics.current_ratio > 1.5) score += 4; else if (metrics.current_ratio > 1) score += 3; else if (metrics.current_ratio > 0.75) score += 2; else score += 1; } // 资产负债率评分 if (metrics.debt_ratio !== null && metrics.debt_ratio !== undefined) { factors++; if (metrics.debt_ratio < 30) score += 5; else if (metrics.debt_ratio < 40) score += 4; else if (metrics.debt_ratio < 50) score += 3; else if (metrics.debt_ratio < 60) score += 2; else if (metrics.debt_ratio < 70) score += 1; } // 营收增长率评分 if (metrics.revenue_growth !== null && metrics.revenue_growth !== undefined) { factors++; if (metrics.revenue_growth > 30) score += 5; else if (metrics.revenue_growth > 20) score += 4; else if (metrics.revenue_growth > 10) score += 3; else if (metrics.revenue_growth > 5) score += 2; else if (metrics.revenue_growth > 0) score += 1; } if (factors === 0) return null; const avgScore = (score / factors) * 20; // 转换为100分制 return { score: avgScore, level: avgScore >= 80 ? '优秀' : avgScore >= 60 ? '良好' : avgScore >= 40 ? '一般' : '较差', color: avgScore >= 80 ? 'green' : avgScore >= 60 ? 'blue' : avgScore >= 40 ? 'yellow' : 'red' }; }, /** * 格式化数据表格列配置 * @param {string} type - 表格类型 */ getTableColumns: (type) => { const columnConfigs = { balanceSheet: [ { key: 'period', label: '报告期', align: 'left' }, { key: 'total_assets', label: '总资产', align: 'right', format: 'largeNumber' }, { key: 'total_liabilities', label: '总负债', align: 'right', format: 'largeNumber' }, { key: 'total_equity', label: '股东权益', align: 'right', format: 'largeNumber' }, { key: 'current_ratio', label: '流动比率', align: 'right', format: 'decimal' }, { key: 'debt_ratio', label: '资产负债率', align: 'right', format: 'percent' }, ], incomeStatement: [ { key: 'period', label: '报告期', align: 'left' }, { key: 'revenue', label: '营业收入', align: 'right', format: 'largeNumber' }, { key: 'operating_profit', label: '营业利润', align: 'right', format: 'largeNumber' }, { key: 'net_profit', label: '净利润', align: 'right', format: 'largeNumber' }, { key: 'gross_margin', label: '毛利率', align: 'right', format: 'percent' }, { key: 'net_margin', label: '净利率', align: 'right', format: 'percent' }, ], cashflow: [ { key: 'period', label: '报告期', align: 'left' }, { key: 'operating_cashflow', label: '经营现金流', align: 'right', format: 'largeNumber' }, { key: 'investing_cashflow', label: '投资现金流', align: 'right', format: 'largeNumber' }, { key: 'financing_cashflow', label: '筹资现金流', align: 'right', format: 'largeNumber' }, { key: 'free_cashflow', label: '自由现金流', align: 'right', format: 'largeNumber' }, ], metrics: [ { key: 'period', label: '报告期', align: 'left' }, { key: 'roe', label: 'ROE', align: 'right', format: 'percent' }, { key: 'roa', label: 'ROA', align: 'right', format: 'percent' }, { key: 'eps', label: 'EPS', align: 'right', format: 'decimal' }, { key: 'pe', label: 'PE', align: 'right', format: 'decimal' }, { key: 'pb', label: 'PB', align: 'right', format: 'decimal' }, ] }; return columnConfigs[type] || []; } }; // 图表数据处理工具 export const chartUtils = { /** * 准备趋势图数据 * @param {array} data - 原始数据 * @param {array} metrics - 要显示的指标 */ prepareTrendData: (data, metrics) => { return data.map(item => { const point = { period: formatUtils.getReportType(item.period) }; metrics.forEach(metric => { point[metric.key] = item[metric.dataPath]; }); return point; }).reverse(); // 按时间顺序排列 }, /** * 准备饼图数据 * @param {array} data - 原始数据 * @param {string} valueKey - 值字段 * @param {string} nameKey - 名称字段 */ preparePieData: (data, valueKey, nameKey) => { const total = data.reduce((sum, item) => sum + (item[valueKey] || 0), 0); return data.map(item => ({ name: item[nameKey], value: item[valueKey], percentage: total > 0 ? (item[valueKey] / total * 100).toFixed(2) : 0 })); }, /** * 准备对比柱状图数据 * @param {array} data - 原始数据 * @param {array} periods - 要对比的期间 * @param {array} metrics - 要对比的指标 */ prepareComparisonData: (data, periods, metrics) => { return metrics.map(metric => ({ metric: metric.label, ...periods.reduce((acc, period, index) => { const periodData = data.find(d => d.period === period); acc[`period${index + 1}`] = periodData ? periodData[metric.key] : 0; return acc; }, {}) })); }, /** * 获取图表颜色配置 * @param {string} theme - 主题类型 */ getChartColors: (theme = 'default') => { const themes = { default: ['#3182CE', '#38A169', '#DD6B20', '#805AD5', '#D69E2E', '#E53E3E'], blue: ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8', '#82CA9D'], green: ['#38A169', '#48BB78', '#68D391', '#9AE6B4', '#C6F6D5', '#F0FFF4'] }; return themes[theme] || themes.default; } }; export default financialService;