refactor(ForecastReport): 合并营收/利润趋势与增长率图表
- 新增 IncomeProfitGrowthChart 合并组件 - 柱状图显示营业收入(左Y轴) - 折线图显示净利润(左Y轴,渐变填充) - 虚线显示增长率(右Y轴,红涨绿跌) - 布局调整:合并图表独占一行,EPS/PE-PEG 两列 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* 营业收入、净利润趋势与增长率分析 - 合并图表
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import ReactECharts from 'echarts-for-react';
|
||||||
|
import ChartCard from './ChartCard';
|
||||||
|
import { CHART_COLORS, BASE_CHART_CONFIG, THEME } from '../constants';
|
||||||
|
import type { IncomeProfitTrend, GrowthBars } from '../types';
|
||||||
|
|
||||||
|
interface IncomeProfitGrowthChartProps {
|
||||||
|
incomeProfitData: IncomeProfitTrend;
|
||||||
|
growthData: GrowthBars;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IncomeProfitGrowthChart: React.FC<IncomeProfitGrowthChartProps> = ({
|
||||||
|
incomeProfitData,
|
||||||
|
growthData,
|
||||||
|
}) => {
|
||||||
|
const option = useMemo(() => ({
|
||||||
|
...BASE_CHART_CONFIG,
|
||||||
|
tooltip: {
|
||||||
|
...BASE_CHART_CONFIG.tooltip,
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
crossStyle: {
|
||||||
|
color: 'rgba(212, 175, 55, 0.5)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
...BASE_CHART_CONFIG.legend,
|
||||||
|
data: ['营业总收入', '归母净利润', '营收增长率'],
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: 60,
|
||||||
|
bottom: 50,
|
||||||
|
top: 40,
|
||||||
|
containLabel: false,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
...BASE_CHART_CONFIG.xAxis,
|
||||||
|
type: 'category',
|
||||||
|
data: incomeProfitData.years,
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
...BASE_CHART_CONFIG.yAxis,
|
||||||
|
type: 'value',
|
||||||
|
name: '金额(百万元)',
|
||||||
|
position: 'left',
|
||||||
|
nameTextStyle: { color: THEME.textSecondary },
|
||||||
|
axisLabel: {
|
||||||
|
color: THEME.textSecondary,
|
||||||
|
formatter: (value: number) => {
|
||||||
|
if (Math.abs(value) >= 1000) {
|
||||||
|
return (value / 1000).toFixed(0) + 'k';
|
||||||
|
}
|
||||||
|
return value.toFixed(0);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...BASE_CHART_CONFIG.yAxis,
|
||||||
|
type: 'value',
|
||||||
|
name: '增长率(%)',
|
||||||
|
position: 'right',
|
||||||
|
nameTextStyle: { color: THEME.textSecondary },
|
||||||
|
axisLabel: {
|
||||||
|
color: THEME.textSecondary,
|
||||||
|
formatter: '{value}%',
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '营业总收入',
|
||||||
|
type: 'bar',
|
||||||
|
data: incomeProfitData.income,
|
||||||
|
itemStyle: {
|
||||||
|
color: CHART_COLORS.income,
|
||||||
|
},
|
||||||
|
barMaxWidth: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '归母净利润',
|
||||||
|
type: 'line',
|
||||||
|
data: incomeProfitData.profit,
|
||||||
|
smooth: true,
|
||||||
|
lineStyle: { width: 2, color: CHART_COLORS.profit },
|
||||||
|
itemStyle: { color: CHART_COLORS.profit },
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(246, 173, 85, 0.3)' },
|
||||||
|
{ offset: 1, color: 'rgba(246, 173, 85, 0.05)' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '营收增长率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: growthData.revenue_growth_pct,
|
||||||
|
smooth: true,
|
||||||
|
lineStyle: { width: 2, type: 'dashed', color: '#10B981' },
|
||||||
|
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 !== null && params.value !== undefined
|
||||||
|
? `${params.value.toFixed(1)}%`
|
||||||
|
: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}), [incomeProfitData, growthData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartCard title="营业收入与净利润趋势 · 增长率分析" height={320}>
|
||||||
|
<ReactECharts option={option} style={{ height: 320 }} />
|
||||||
|
</ChartCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IncomeProfitGrowthChart;
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
export { default as ChartCard } from './ChartCard';
|
export { default as ChartCard } from './ChartCard';
|
||||||
export { default as IncomeProfitChart } from './IncomeProfitChart';
|
export { default as IncomeProfitChart } from './IncomeProfitChart';
|
||||||
export { default as GrowthChart } from './GrowthChart';
|
export { default as GrowthChart } from './GrowthChart';
|
||||||
|
export { default as IncomeProfitGrowthChart } from './IncomeProfitGrowthChart';
|
||||||
export { default as EpsChart } from './EpsChart';
|
export { default as EpsChart } from './EpsChart';
|
||||||
export { default as PePegChart } from './PePegChart';
|
export { default as PePegChart } from './PePegChart';
|
||||||
export { default as DetailTable } from './DetailTable';
|
export { default as DetailTable } from './DetailTable';
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import { Box, SimpleGrid, HStack, Heading, Skeleton, IconButton } from '@chakra-
|
|||||||
import { RefreshCw } from 'lucide-react';
|
import { RefreshCw } from 'lucide-react';
|
||||||
import { stockService } from '@services/eventService';
|
import { stockService } from '@services/eventService';
|
||||||
import {
|
import {
|
||||||
IncomeProfitChart,
|
IncomeProfitGrowthChart,
|
||||||
GrowthChart,
|
|
||||||
EpsChart,
|
EpsChart,
|
||||||
PePegChart,
|
PePegChart,
|
||||||
DetailTable,
|
DetailTable,
|
||||||
@@ -94,12 +93,20 @@ const ForecastReport: React.FC<ForecastReportProps> = ({ stockCode: propStockCod
|
|||||||
|
|
||||||
{/* 图表区域 */}
|
{/* 图表区域 */}
|
||||||
{data && (
|
{data && (
|
||||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
<>
|
||||||
<IncomeProfitChart data={data.income_profit_trend} />
|
{/* 合并图表:营收/利润/增长率 */}
|
||||||
<GrowthChart data={data.growth_bars} />
|
<Box mb={4}>
|
||||||
<EpsChart data={data.eps_trend} />
|
<IncomeProfitGrowthChart
|
||||||
<PePegChart data={data.pe_peg_axes} />
|
incomeProfitData={data.income_profit_trend}
|
||||||
</SimpleGrid>
|
growthData={data.growth_bars}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{/* EPS 和 PE/PEG */}
|
||||||
|
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
||||||
|
<EpsChart data={data.eps_trend} />
|
||||||
|
<PePegChart data={data.pe_peg_axes} />
|
||||||
|
</SimpleGrid>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 详细数据表格 */}
|
{/* 详细数据表格 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user