refactor(ForecastReport): 迁移至 TypeScript
This commit is contained in:
115
src/views/Company/components/ForecastReport/index.tsx
Normal file
115
src/views/Company/components/ForecastReport/index.tsx
Normal file
@@ -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<ForecastReportProps> = ({ stockCode: propStockCode }) => {
|
||||
const [code, setCode] = useState(propStockCode || '600000');
|
||||
const [data, setData] = useState<ForecastData | null>(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 (
|
||||
<Box>
|
||||
{/* 标题栏 */}
|
||||
<HStack align="center" justify="space-between" mb={4}>
|
||||
<Heading size="md" color={THEME.gold}>
|
||||
盈利预测报表
|
||||
</Heading>
|
||||
<IconButton
|
||||
icon={<RefreshCw size={14} className={loading ? 'spin' : ''} />}
|
||||
onClick={load}
|
||||
isLoading={loading}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
aria-label="刷新数据"
|
||||
borderColor={THEME.goldBorder}
|
||||
color={THEME.gold}
|
||||
_hover={{
|
||||
bg: THEME.goldLight,
|
||||
borderColor: THEME.gold,
|
||||
}}
|
||||
/>
|
||||
<style>{`
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
`}</style>
|
||||
</HStack>
|
||||
|
||||
{/* 加载骨架屏 */}
|
||||
{loading && !data && (
|
||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<ChartCard key={i} title="加载中...">
|
||||
<Skeleton height={`${CHART_HEIGHT}px`} />
|
||||
</ChartCard>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
|
||||
{/* 图表区域 */}
|
||||
{data && (
|
||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
||||
<IncomeProfitChart data={data.income_profit_trend} />
|
||||
<GrowthChart data={data.growth_bars} />
|
||||
<EpsChart data={data.eps_trend} />
|
||||
<PePegChart data={data.pe_peg_axes} />
|
||||
</SimpleGrid>
|
||||
)}
|
||||
|
||||
{/* 详细数据表格 */}
|
||||
{data && (
|
||||
<Box mt={4}>
|
||||
<DetailTable data={data.detail_table} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForecastReport;
|
||||
Reference in New Issue
Block a user