162 lines
6.8 KiB
JavaScript
162 lines
6.8 KiB
JavaScript
// 简易版公司盈利预测报表视图
|
|
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]);
|
|
|
|
// 加载数据
|
|
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 (
|
|
<Box p={4}>
|
|
<HStack align="center" justify="space-between" mb={4}>
|
|
<Heading size="md">盈利预测报表</Heading>
|
|
<Button
|
|
leftIcon={<RepeatIcon />}
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={load}
|
|
isLoading={loading}
|
|
>
|
|
刷新数据
|
|
</Button>
|
|
</HStack>
|
|
|
|
{loading && !data && (
|
|
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
|
{[1,2,3,4].map(i => (
|
|
<Card key={i}>
|
|
<CardHeader><Skeleton height="18px" width="140px" /></CardHeader>
|
|
<CardBody>
|
|
<Skeleton height="320px" />
|
|
</CardBody>
|
|
</Card>
|
|
))}
|
|
</SimpleGrid>
|
|
)}
|
|
|
|
{data && (
|
|
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
|
<Card><CardHeader><Heading size="sm">营业收入与净利润趋势</Heading></CardHeader><CardBody><ReactECharts option={incomeProfitOption} style={{ height: 320 }} /></CardBody></Card>
|
|
<Card><CardHeader><Heading size="sm">增长率分析</Heading></CardHeader><CardBody><ReactECharts option={growthOption} style={{ height: 320 }} /></CardBody></Card>
|
|
<Card><CardHeader><Heading size="sm">EPS 趋势</Heading></CardHeader><CardBody><ReactECharts option={epsOption} style={{ height: 320 }} /></CardBody></Card>
|
|
<Card><CardHeader><Heading size="sm">PE 与 PEG 分析</Heading></CardHeader><CardBody><ReactECharts option={pePegOption} style={{ height: 320 }} /></CardBody></Card>
|
|
</SimpleGrid>
|
|
)}
|
|
|
|
{data && (
|
|
<Card mt={4}>
|
|
<CardHeader><Heading size="sm">详细数据表格</Heading></CardHeader>
|
|
<CardBody>
|
|
<Table size="sm" variant="simple">
|
|
<Thead>
|
|
<Tr>
|
|
<Th>关键指标</Th>
|
|
{years.map(y => <Th key={y}>{y}</Th>)}
|
|
</Tr>
|
|
</Thead>
|
|
<Tbody>
|
|
{data.detail_table.rows.map((row, idx) => (
|
|
<Tr key={idx}>
|
|
<Td><Tag>{row['指标']}</Tag></Td>
|
|
{years.map(y => <Td key={y}>{row[y] ?? '-'}</Td>)}
|
|
</Tr>
|
|
))}
|
|
</Tbody>
|
|
</Table>
|
|
</CardBody>
|
|
</Card>
|
|
)}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default ForecastReport;
|
|
|
|
|