From ba99f55b16783f7e19ecbee2c612f62aef648bbd Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 16 Dec 2025 20:28:58 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ForecastReport):=20=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E8=87=B3=20TypeScript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ForecastReport/components/ChartCard.tsx | 37 ++++ .../ForecastReport/components/DetailTable.tsx | 148 ++++++++++++++++ .../ForecastReport/components/EpsChart.tsx | 51 ++++++ .../ForecastReport/components/GrowthChart.tsx | 59 +++++++ .../components/IncomeProfitChart.tsx | 69 ++++++++ .../ForecastReport/components/PePegChart.tsx | 68 ++++++++ .../ForecastReport/components/index.ts | 10 ++ .../components/ForecastReport/constants.ts | 84 +++++++++ .../components/ForecastReport/index.js | 161 ------------------ .../components/ForecastReport/index.tsx | 115 +++++++++++++ .../components/ForecastReport/types.ts | 81 +++++++++ 11 files changed, 722 insertions(+), 161 deletions(-) create mode 100644 src/views/Company/components/ForecastReport/components/ChartCard.tsx create mode 100644 src/views/Company/components/ForecastReport/components/DetailTable.tsx create mode 100644 src/views/Company/components/ForecastReport/components/EpsChart.tsx create mode 100644 src/views/Company/components/ForecastReport/components/GrowthChart.tsx create mode 100644 src/views/Company/components/ForecastReport/components/IncomeProfitChart.tsx create mode 100644 src/views/Company/components/ForecastReport/components/PePegChart.tsx create mode 100644 src/views/Company/components/ForecastReport/components/index.ts create mode 100644 src/views/Company/components/ForecastReport/constants.ts delete mode 100644 src/views/Company/components/ForecastReport/index.js create mode 100644 src/views/Company/components/ForecastReport/index.tsx create mode 100644 src/views/Company/components/ForecastReport/types.ts diff --git a/src/views/Company/components/ForecastReport/components/ChartCard.tsx b/src/views/Company/components/ForecastReport/components/ChartCard.tsx new file mode 100644 index 00000000..86a86c20 --- /dev/null +++ b/src/views/Company/components/ForecastReport/components/ChartCard.tsx @@ -0,0 +1,37 @@ +/** + * 通用图表卡片组件 - 黑金主题 + */ + +import React from 'react'; +import { Box, Heading } from '@chakra-ui/react'; +import { THEME } from '../constants'; +import type { ChartCardProps } from '../types'; + +const ChartCard: React.FC = ({ title, children }) => { + return ( + + + + {title} + + + + {children} + + + ); +}; + +export default ChartCard; diff --git a/src/views/Company/components/ForecastReport/components/DetailTable.tsx b/src/views/Company/components/ForecastReport/components/DetailTable.tsx new file mode 100644 index 00000000..85c1c8f2 --- /dev/null +++ b/src/views/Company/components/ForecastReport/components/DetailTable.tsx @@ -0,0 +1,148 @@ +/** + * 详细数据表格 - 纯 Ant Design 黑金主题 + */ + +import React, { useMemo } from 'react'; +import { Table, ConfigProvider, Tag, theme as antTheme } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import type { DetailTableProps, DetailTableRow } from '../types'; + +// Ant Design 黑金主题配置 +const BLACK_GOLD_THEME = { + algorithm: antTheme.darkAlgorithm, + token: { + colorPrimary: '#D4AF37', + colorBgContainer: '#1A202C', + colorBgElevated: '#1a1a2e', + colorBorder: 'rgba(212, 175, 55, 0.3)', + colorText: '#e0e0e0', + colorTextSecondary: '#a0a0a0', + borderRadius: 4, + fontSize: 13, + }, + components: { + Table: { + headerBg: 'rgba(212, 175, 55, 0.1)', + headerColor: '#D4AF37', + rowHoverBg: 'rgba(212, 175, 55, 0.05)', + borderColor: 'rgba(212, 175, 55, 0.2)', + cellPaddingBlock: 8, + cellPaddingInline: 12, + }, + }, +}; + +// 表格样式 +const tableStyles = ` + .forecast-detail-table { + background: #1A202C; + border: 1px solid rgba(212, 175, 55, 0.3); + border-radius: 6px; + overflow: hidden; + } + .forecast-detail-table .table-header { + padding: 12px 16px; + border-bottom: 1px solid rgba(212, 175, 55, 0.3); + background: rgba(212, 175, 55, 0.1); + } + .forecast-detail-table .table-header h4 { + margin: 0; + color: #D4AF37; + font-size: 14px; + font-weight: 600; + } + .forecast-detail-table .table-body { + padding: 16px; + } + .forecast-detail-table .ant-table-cell-fix-left, + .forecast-detail-table .ant-table-cell-fix-right { + background: #1A202C !important; + } + .forecast-detail-table .ant-table-thead .ant-table-cell-fix-left, + .forecast-detail-table .ant-table-thead .ant-table-cell-fix-right { + background: rgba(26, 32, 44, 0.95) !important; + } + .forecast-detail-table .ant-table-tbody > tr:hover > td { + background: rgba(212, 175, 55, 0.08) !important; + } + .forecast-detail-table .ant-table-tbody > tr:hover > td.ant-table-cell-fix-left { + background: #242d3d !important; + } + .forecast-detail-table .ant-table-tbody > tr > td { + background: #1A202C !important; + } + .forecast-detail-table .metric-tag { + background: rgba(212, 175, 55, 0.15); + border-color: rgba(212, 175, 55, 0.3); + color: #D4AF37; + } +`; + +interface TableRowData extends DetailTableRow { + key: string; +} + +const DetailTable: React.FC = ({ data }) => { + const { years, rows } = data; + + // 构建列配置 + const columns: ColumnsType = useMemo(() => { + const cols: ColumnsType = [ + { + title: '关键指标', + dataIndex: '指标', + key: '指标', + fixed: 'left', + width: 160, + render: (value: string) => ( + {value} + ), + }, + ]; + + // 添加年份列 + years.forEach((year) => { + cols.push({ + title: year, + dataIndex: year, + key: year, + align: 'right', + width: 100, + render: (value: string | number | null) => value ?? '-', + }); + }); + + return cols; + }, [years]); + + // 构建数据源 + const dataSource: TableRowData[] = useMemo(() => { + return rows.map((row, idx) => ({ + ...row, + key: `row-${idx}`, + })); + }, [rows]); + + return ( +
+ +
+

详细数据表格

+
+
+ + + columns={columns} + dataSource={dataSource} + pagination={false} + size="small" + scroll={{ x: 'max-content' }} + bordered + /> + +
+
+ ); +}; + +export default DetailTable; diff --git a/src/views/Company/components/ForecastReport/components/EpsChart.tsx b/src/views/Company/components/ForecastReport/components/EpsChart.tsx new file mode 100644 index 00000000..64b1dc68 --- /dev/null +++ b/src/views/Company/components/ForecastReport/components/EpsChart.tsx @@ -0,0 +1,51 @@ +/** + * EPS 趋势图 + */ + +import React, { useMemo } from 'react'; +import ReactECharts from 'echarts-for-react'; +import ChartCard from './ChartCard'; +import { CHART_COLORS, BASE_CHART_CONFIG, CHART_HEIGHT, THEME } from '../constants'; +import type { EpsChartProps } from '../types'; + +const EpsChart: React.FC = ({ data }) => { + const option = useMemo(() => ({ + ...BASE_CHART_CONFIG, + color: [CHART_COLORS.eps], + tooltip: { + ...BASE_CHART_CONFIG.tooltip, + trigger: 'axis', + }, + xAxis: { + ...BASE_CHART_CONFIG.xAxis, + type: 'category', + data: data.years, + }, + yAxis: { + ...BASE_CHART_CONFIG.yAxis, + type: 'value', + name: '元/股', + nameTextStyle: { color: THEME.textSecondary }, + }, + series: [ + { + name: 'EPS(稀释)', + type: 'line', + data: data.eps, + smooth: true, + lineStyle: { width: 2 }, + areaStyle: { opacity: 0.15 }, + symbol: 'circle', + symbolSize: 6, + }, + ], + }), [data]); + + return ( + + + + ); +}; + +export default EpsChart; diff --git a/src/views/Company/components/ForecastReport/components/GrowthChart.tsx b/src/views/Company/components/ForecastReport/components/GrowthChart.tsx new file mode 100644 index 00000000..90f2808c --- /dev/null +++ b/src/views/Company/components/ForecastReport/components/GrowthChart.tsx @@ -0,0 +1,59 @@ +/** + * 增长率分析图 + */ + +import React, { useMemo } from 'react'; +import ReactECharts from 'echarts-for-react'; +import ChartCard from './ChartCard'; +import { BASE_CHART_CONFIG, CHART_HEIGHT, THEME } from '../constants'; +import type { GrowthChartProps } from '../types'; + +const GrowthChart: React.FC = ({ data }) => { + const option = useMemo(() => ({ + ...BASE_CHART_CONFIG, + tooltip: { + ...BASE_CHART_CONFIG.tooltip, + trigger: 'axis', + }, + xAxis: { + ...BASE_CHART_CONFIG.xAxis, + type: 'category', + data: data.years, + }, + yAxis: { + ...BASE_CHART_CONFIG.yAxis, + type: 'value', + axisLabel: { + ...BASE_CHART_CONFIG.yAxis.axisLabel, + formatter: '{value}%', + }, + }, + series: [ + { + name: '营收增长率(%)', + type: 'bar', + data: data.revenue_growth_pct, + itemStyle: { + color: (params: { value: number }) => + params.value >= 0 ? THEME.positive : THEME.negative, + }, + label: { + show: true, + position: 'top', + color: THEME.textSecondary, + fontSize: 10, + formatter: (params: { value: number }) => + params.value ? `${params.value.toFixed(1)}%` : '', + }, + }, + ], + }), [data]); + + return ( + + + + ); +}; + +export default GrowthChart; diff --git a/src/views/Company/components/ForecastReport/components/IncomeProfitChart.tsx b/src/views/Company/components/ForecastReport/components/IncomeProfitChart.tsx new file mode 100644 index 00000000..b59249f3 --- /dev/null +++ b/src/views/Company/components/ForecastReport/components/IncomeProfitChart.tsx @@ -0,0 +1,69 @@ +/** + * 营业收入与净利润趋势图 + */ + +import React, { useMemo } from 'react'; +import ReactECharts from 'echarts-for-react'; +import ChartCard from './ChartCard'; +import { CHART_COLORS, BASE_CHART_CONFIG, CHART_HEIGHT, THEME } from '../constants'; +import type { IncomeProfitChartProps } from '../types'; + +const IncomeProfitChart: React.FC = ({ data }) => { + const option = useMemo(() => ({ + ...BASE_CHART_CONFIG, + color: [CHART_COLORS.income, CHART_COLORS.profit], + tooltip: { + ...BASE_CHART_CONFIG.tooltip, + trigger: 'axis', + }, + legend: { + ...BASE_CHART_CONFIG.legend, + data: ['营业总收入(百万元)', '归母净利润(百万元)'], + }, + xAxis: { + ...BASE_CHART_CONFIG.xAxis, + type: 'category', + data: data.years, + }, + yAxis: [ + { + ...BASE_CHART_CONFIG.yAxis, + type: 'value', + name: '收入(百万元)', + nameTextStyle: { color: THEME.textSecondary }, + }, + { + ...BASE_CHART_CONFIG.yAxis, + type: 'value', + name: '利润(百万元)', + nameTextStyle: { color: THEME.textSecondary }, + }, + ], + series: [ + { + name: '营业总收入(百万元)', + type: 'line', + data: data.income, + smooth: true, + lineStyle: { width: 2 }, + areaStyle: { opacity: 0.1 }, + }, + { + name: '归母净利润(百万元)', + type: 'line', + yAxisIndex: 1, + data: data.profit, + smooth: true, + lineStyle: { width: 2 }, + }, + ], + }), [data]); + + return ( + + + + ); +}; + +export default IncomeProfitChart; diff --git a/src/views/Company/components/ForecastReport/components/PePegChart.tsx b/src/views/Company/components/ForecastReport/components/PePegChart.tsx new file mode 100644 index 00000000..dfb5518d --- /dev/null +++ b/src/views/Company/components/ForecastReport/components/PePegChart.tsx @@ -0,0 +1,68 @@ +/** + * PE 与 PEG 分析图 + */ + +import React, { useMemo } from 'react'; +import ReactECharts from 'echarts-for-react'; +import ChartCard from './ChartCard'; +import { CHART_COLORS, BASE_CHART_CONFIG, CHART_HEIGHT, THEME } from '../constants'; +import type { PePegChartProps } from '../types'; + +const PePegChart: React.FC = ({ data }) => { + const option = useMemo(() => ({ + ...BASE_CHART_CONFIG, + color: [CHART_COLORS.pe, CHART_COLORS.peg], + tooltip: { + ...BASE_CHART_CONFIG.tooltip, + trigger: 'axis', + }, + legend: { + ...BASE_CHART_CONFIG.legend, + data: ['PE', 'PEG'], + }, + xAxis: { + ...BASE_CHART_CONFIG.xAxis, + type: 'category', + data: data.years, + }, + yAxis: [ + { + ...BASE_CHART_CONFIG.yAxis, + type: 'value', + name: 'PE(倍)', + nameTextStyle: { color: THEME.textSecondary }, + }, + { + ...BASE_CHART_CONFIG.yAxis, + type: 'value', + name: 'PEG', + nameTextStyle: { color: THEME.textSecondary }, + }, + ], + series: [ + { + name: 'PE', + type: 'line', + data: data.pe, + smooth: true, + lineStyle: { width: 2 }, + }, + { + name: 'PEG', + type: 'line', + yAxisIndex: 1, + data: data.peg, + smooth: true, + lineStyle: { width: 2 }, + }, + ], + }), [data]); + + return ( + + + + ); +}; + +export default PePegChart; diff --git a/src/views/Company/components/ForecastReport/components/index.ts b/src/views/Company/components/ForecastReport/components/index.ts new file mode 100644 index 00000000..8a4e8261 --- /dev/null +++ b/src/views/Company/components/ForecastReport/components/index.ts @@ -0,0 +1,10 @@ +/** + * ForecastReport 子组件导出 + */ + +export { default as ChartCard } from './ChartCard'; +export { default as IncomeProfitChart } from './IncomeProfitChart'; +export { default as GrowthChart } from './GrowthChart'; +export { default as EpsChart } from './EpsChart'; +export { default as PePegChart } from './PePegChart'; +export { default as DetailTable } from './DetailTable'; diff --git a/src/views/Company/components/ForecastReport/constants.ts b/src/views/Company/components/ForecastReport/constants.ts new file mode 100644 index 00000000..83e32274 --- /dev/null +++ b/src/views/Company/components/ForecastReport/constants.ts @@ -0,0 +1,84 @@ +/** + * 盈利预测报表常量和图表配置 + */ + +// 黑金主题配色 +export const THEME = { + gold: '#D4AF37', + goldLight: 'rgba(212, 175, 55, 0.1)', + goldBorder: 'rgba(212, 175, 55, 0.3)', + bgDark: '#1A202C', + text: '#E2E8F0', + textSecondary: '#A0AEC0', + positive: '#E53E3E', + negative: '#10B981', +}; + +// 图表配色方案 +export const CHART_COLORS = { + income: '#D4AF37', // 收入 - 金色 + profit: '#F6AD55', // 利润 - 橙金色 + growth: '#B8860B', // 增长 - 深金色 + eps: '#DAA520', // EPS - 金菊色 + pe: '#D4AF37', // PE - 金色 + peg: '#CD853F', // PEG - 秘鲁色 +}; + +// ECharts 基础配置(黑金主题) +export const BASE_CHART_CONFIG = { + backgroundColor: 'transparent', + textStyle: { + color: THEME.text, + }, + tooltip: { + backgroundColor: 'rgba(26, 32, 44, 0.95)', + borderColor: THEME.goldBorder, + textStyle: { + color: THEME.text, + }, + }, + legend: { + textStyle: { + color: THEME.textSecondary, + }, + }, + grid: { + left: 50, + right: 20, + bottom: 40, + top: 40, + containLabel: false, + }, + xAxis: { + axisLine: { + lineStyle: { + color: THEME.goldBorder, + }, + }, + axisLabel: { + color: THEME.textSecondary, + rotate: 30, + }, + splitLine: { + show: false, + }, + }, + yAxis: { + axisLine: { + lineStyle: { + color: THEME.goldBorder, + }, + }, + axisLabel: { + color: THEME.textSecondary, + }, + splitLine: { + lineStyle: { + color: 'rgba(212, 175, 55, 0.1)', + }, + }, + }, +}; + +// 图表高度 +export const CHART_HEIGHT = 280; diff --git a/src/views/Company/components/ForecastReport/index.js b/src/views/Company/components/ForecastReport/index.js deleted file mode 100644 index f42955e2..00000000 --- a/src/views/Company/components/ForecastReport/index.js +++ /dev/null @@ -1,161 +0,0 @@ -// 简易版公司盈利预测报表视图 -import React, { useState, useEffect } from 'react'; -import { Box, Flex, Input, Button, SimpleGrid, HStack, Text, Skeleton, VStack } from '@chakra-ui/react'; -import { Card, CardHeader, CardBody, Heading, Table, Thead, Tr, Th, Tbody, Td, Tag } from '@chakra-ui/react'; -import { RepeatIcon } from '@chakra-ui/icons'; -import ReactECharts from 'echarts-for-react'; -import { stockService } from '@services/eventService'; - -const ForecastReport = ({ stockCode: propStockCode }) => { - const [code, setCode] = useState(propStockCode || '600000'); - const [data, setData] = useState(null); - const [loading, setLoading] = useState(false); - - const load = async () => { - if (!code) return; - setLoading(true); - try { - const resp = await stockService.getForecastReport(code); - if (resp && resp.success) setData(resp.data); - } finally { - setLoading(false); - } - }; - - // 监听props中的stockCode变化 - useEffect(() => { - if (propStockCode && propStockCode !== code) { - setCode(propStockCode); - } - }, [propStockCode, code]); - - // 加载数据 - useEffect(() => { - if (code) { - load(); - } - }, [code]); - - const years = data?.detail_table?.years || []; - - const colors = ['#805AD5', '#38B2AC', '#F6AD55', '#63B3ED', '#E53E3E', '#10B981']; - - const incomeProfitOption = data ? { - color: [colors[0], colors[4]], - tooltip: { trigger: 'axis' }, - legend: { data: ['营业总收入(百万元)', '归母净利润(百万元)'] }, - grid: { left: 40, right: 20, bottom: 40, top: 30 }, - xAxis: { type: 'category', data: data.income_profit_trend.years, axisLabel: { rotate: 30 } }, - yAxis: [ - { type: 'value', name: '收入(百万元)' }, - { type: 'value', name: '利润(百万元)' } - ], - series: [ - { name: '营业总收入(百万元)', type: 'line', data: data.income_profit_trend.income, smooth: true, lineStyle: { width: 2 }, areaStyle: { opacity: 0.08 } }, - { name: '归母净利润(百万元)', type: 'line', yAxisIndex: 1, data: data.income_profit_trend.profit, smooth: true, lineStyle: { width: 2 } } - ] - } : {}; - - const growthOption = data ? { - color: [colors[2]], - tooltip: { trigger: 'axis' }, - grid: { left: 40, right: 20, bottom: 40, top: 30 }, - xAxis: { type: 'category', data: data.growth_bars.years, axisLabel: { rotate: 30 } }, - yAxis: { type: 'value', axisLabel: { formatter: '{value}%' } }, - series: [ { - name: '营收增长率(%)', - type: 'bar', - data: data.growth_bars.revenue_growth_pct, - itemStyle: { color: (params) => params.value >= 0 ? '#E53E3E' : '#10B981' } - } ] - } : {}; - - const epsOption = data ? { - color: [colors[3]], - tooltip: { trigger: 'axis' }, - grid: { left: 40, right: 20, bottom: 40, top: 30 }, - xAxis: { type: 'category', data: data.eps_trend.years, axisLabel: { rotate: 30 } }, - yAxis: { type: 'value', name: '元/股' }, - series: [ { name: 'EPS(稀释)', type: 'line', data: data.eps_trend.eps, smooth: true, areaStyle: { opacity: 0.1 }, lineStyle: { width: 2 } } ] - } : {}; - - const pePegOption = data ? { - color: [colors[0], colors[1]], - tooltip: { trigger: 'axis' }, - legend: { data: ['PE', 'PEG'] }, - grid: { left: 40, right: 40, bottom: 40, top: 30 }, - xAxis: { type: 'category', data: data.pe_peg_axes.years, axisLabel: { rotate: 30 } }, - yAxis: [ { type: 'value', name: 'PE(倍)' }, { type: 'value', name: 'PEG' } ], - series: [ - { name: 'PE', type: 'line', data: data.pe_peg_axes.pe, smooth: true }, - { name: 'PEG', type: 'line', yAxisIndex: 1, data: data.pe_peg_axes.peg, smooth: true } - ] - } : {}; - - return ( - - - 盈利预测报表 - - - - {loading && !data && ( - - {[1,2,3,4].map(i => ( - - - - - - - ))} - - )} - - {data && ( - - 营业收入与净利润趋势 - 增长率分析 - EPS 趋势 - PE 与 PEG 分析 - - )} - - {data && ( - - 详细数据表格 - - - - - - {years.map(y => )} - - - - {data.detail_table.rows.map((row, idx) => ( - - - {years.map(y => )} - - ))} - -
关键指标{y}
{row['指标']}{row[y] ?? '-'}
-
-
- )} -
- ); -}; - -export default ForecastReport; - - diff --git a/src/views/Company/components/ForecastReport/index.tsx b/src/views/Company/components/ForecastReport/index.tsx new file mode 100644 index 00000000..e15eef63 --- /dev/null +++ b/src/views/Company/components/ForecastReport/index.tsx @@ -0,0 +1,115 @@ +/** + * 盈利预测报表视图 - 黑金主题 + */ + +import React, { useState, useEffect, useCallback } from 'react'; +import { Box, SimpleGrid, HStack, Heading, Skeleton, IconButton } from '@chakra-ui/react'; +import { RefreshCw } from 'lucide-react'; +import { stockService } from '@services/eventService'; +import { + IncomeProfitChart, + GrowthChart, + EpsChart, + PePegChart, + DetailTable, + ChartCard, +} from './components'; +import { THEME, CHART_HEIGHT } from './constants'; +import type { ForecastReportProps, ForecastData } from './types'; + +const ForecastReport: React.FC = ({ stockCode: propStockCode }) => { + const [code, setCode] = useState(propStockCode || '600000'); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + + const load = useCallback(async () => { + if (!code) return; + setLoading(true); + try { + const resp = await stockService.getForecastReport(code); + if (resp && resp.success) { + setData(resp.data); + } + } finally { + setLoading(false); + } + }, [code]); + + // 监听 props 中的 stockCode 变化 + useEffect(() => { + if (propStockCode && propStockCode !== code) { + setCode(propStockCode); + } + }, [propStockCode, code]); + + // 加载数据 + useEffect(() => { + if (code) { + load(); + } + }, [code, load]); + + return ( + + {/* 标题栏 */} + + + 盈利预测报表 + + } + onClick={load} + isLoading={loading} + variant="outline" + size="sm" + aria-label="刷新数据" + borderColor={THEME.goldBorder} + color={THEME.gold} + _hover={{ + bg: THEME.goldLight, + borderColor: THEME.gold, + }} + /> + + + + {/* 加载骨架屏 */} + {loading && !data && ( + + {[1, 2, 3, 4].map((i) => ( + + + + ))} + + )} + + {/* 图表区域 */} + {data && ( + + + + + + + )} + + {/* 详细数据表格 */} + {data && ( + + + + )} + + ); +}; + +export default ForecastReport; diff --git a/src/views/Company/components/ForecastReport/types.ts b/src/views/Company/components/ForecastReport/types.ts new file mode 100644 index 00000000..d084eca1 --- /dev/null +++ b/src/views/Company/components/ForecastReport/types.ts @@ -0,0 +1,81 @@ +/** + * 盈利预测报表类型定义 + */ + +// 收入利润趋势数据 +export interface IncomeProfitTrend { + years: string[]; + income: number[]; + profit: number[]; +} + +// 增长率数据 +export interface GrowthBars { + years: string[]; + revenue_growth_pct: number[]; +} + +// EPS 趋势数据 +export interface EpsTrend { + years: string[]; + eps: number[]; +} + +// PE/PEG 数据 +export interface PePegAxes { + years: string[]; + pe: number[]; + peg: number[]; +} + +// 详细表格行数据 +export interface DetailTableRow { + 指标: string; + [year: string]: string | number | null; +} + +// 详细表格数据 +export interface DetailTable { + years: string[]; + rows: DetailTableRow[]; +} + +// 完整的预测报表数据 +export interface ForecastData { + income_profit_trend: IncomeProfitTrend; + growth_bars: GrowthBars; + eps_trend: EpsTrend; + pe_peg_axes: PePegAxes; + detail_table: DetailTable; +} + +// 组件 Props +export interface ForecastReportProps { + stockCode?: string; +} + +export interface ChartCardProps { + title: string; + children: React.ReactNode; + height?: number; +} + +export interface IncomeProfitChartProps { + data: IncomeProfitTrend; +} + +export interface GrowthChartProps { + data: GrowthBars; +} + +export interface EpsChartProps { + data: EpsTrend; +} + +export interface PePegChartProps { + data: PePegAxes; +} + +export interface DetailTableProps { + data: DetailTable; +}