refactor(FinancialPanorama): 重构为 SubTabContainer 二级导航
- 主组件从 Chakra Tabs 迁移到 SubTabContainer - 新增 PeriodSelector 时间选择器组件 - IndustryRankingView 增加深色主题支持 - 拆分出 6 个独立 Tab 组件到 tabs/ 目录 - 类型定义优化,props 改为可选
This commit is contained in:
@@ -21,14 +21,23 @@ import type { IndustryRankingViewProps } from '../types';
|
||||
|
||||
export const IndustryRankingView: React.FC<IndustryRankingViewProps> = ({
|
||||
industryRank,
|
||||
bgColor,
|
||||
borderColor,
|
||||
bgColor = 'white',
|
||||
borderColor = 'gray.200',
|
||||
textColor,
|
||||
labelColor,
|
||||
}) => {
|
||||
// 判断是否为深色主题
|
||||
const isDarkTheme = bgColor === 'gray.800' || bgColor === 'gray.900';
|
||||
const resolvedTextColor = textColor || (isDarkTheme ? 'white' : 'gray.800');
|
||||
const resolvedLabelColor = labelColor || (isDarkTheme ? 'gray.400' : 'gray.500');
|
||||
const cardBg = isDarkTheme ? 'transparent' : 'white';
|
||||
const headingColor = isDarkTheme ? 'yellow.500' : 'gray.800';
|
||||
|
||||
if (!industryRank || !Array.isArray(industryRank) || industryRank.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<Card bg={cardBg} borderColor={borderColor} borderWidth="1px">
|
||||
<CardBody>
|
||||
<Text textAlign="center" color="gray.500" py={8}>
|
||||
<Text textAlign="center" color={resolvedLabelColor} py={8}>
|
||||
暂无行业排名数据
|
||||
</Text>
|
||||
</CardBody>
|
||||
@@ -39,17 +48,32 @@ export const IndustryRankingView: React.FC<IndustryRankingViewProps> = ({
|
||||
return (
|
||||
<VStack spacing={4} align="stretch">
|
||||
{industryRank.map((periodData, periodIdx) => (
|
||||
<Card key={periodIdx}>
|
||||
<CardHeader>
|
||||
<Card
|
||||
key={periodIdx}
|
||||
bg={cardBg}
|
||||
borderColor={borderColor}
|
||||
borderWidth="1px"
|
||||
>
|
||||
<CardHeader pb={2}>
|
||||
<HStack justify="space-between">
|
||||
<Heading size="sm">{periodData.report_type} 行业排名</Heading>
|
||||
<Badge colorScheme="purple">{periodData.period}</Badge>
|
||||
<Heading size="sm" color={headingColor}>
|
||||
{periodData.report_type} 行业排名
|
||||
</Heading>
|
||||
<Badge
|
||||
bg={isDarkTheme ? 'transparent' : undefined}
|
||||
borderWidth={isDarkTheme ? '1px' : 0}
|
||||
borderColor={isDarkTheme ? 'yellow.600' : undefined}
|
||||
color={isDarkTheme ? 'yellow.500' : undefined}
|
||||
colorScheme={isDarkTheme ? undefined : 'purple'}
|
||||
>
|
||||
{periodData.period}
|
||||
</Badge>
|
||||
</HStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<CardBody pt={2}>
|
||||
{periodData.rankings?.map((ranking, idx) => (
|
||||
<Box key={idx} mb={6}>
|
||||
<Text fontWeight="bold" mb={3}>
|
||||
<Text fontWeight="bold" mb={3} color={resolvedTextColor}>
|
||||
{ranking.industry_name} ({ranking.level_description})
|
||||
</Text>
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing={3}>
|
||||
@@ -65,6 +89,15 @@ export const IndustryRankingView: React.FC<IndustryRankingViewProps> = ({
|
||||
metric.key.includes('margin') ||
|
||||
metric.key === 'roe';
|
||||
|
||||
// 格式化数值
|
||||
const formattedValue = isPercentMetric
|
||||
? formatUtils.formatPercent(metricData.value)
|
||||
: metricData.value?.toFixed(2) ?? '-';
|
||||
|
||||
const formattedAvg = isPercentMetric
|
||||
? formatUtils.formatPercent(metricData.industry_avg)
|
||||
: metricData.industry_avg?.toFixed(2) ?? '-';
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={metric.key}
|
||||
@@ -74,14 +107,12 @@ export const IndustryRankingView: React.FC<IndustryRankingViewProps> = ({
|
||||
borderWidth="1px"
|
||||
borderColor={borderColor}
|
||||
>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
<Text fontSize="xs" color={resolvedLabelColor}>
|
||||
{metric.name}
|
||||
</Text>
|
||||
<HStack mt={1}>
|
||||
<Text fontWeight="bold">
|
||||
{isPercentMetric
|
||||
? formatUtils.formatPercent(metricData.value)
|
||||
: metricData.value?.toFixed(2) || '-'}
|
||||
<HStack mt={1} spacing={2}>
|
||||
<Text fontWeight="bold" fontSize="lg" color={resolvedTextColor}>
|
||||
{formattedValue}
|
||||
</Text>
|
||||
{metricData.rank && (
|
||||
<Badge
|
||||
@@ -92,11 +123,8 @@ export const IndustryRankingView: React.FC<IndustryRankingViewProps> = ({
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
<Text fontSize="xs" color="gray.500" mt={1}>
|
||||
行业均值:{' '}
|
||||
{isPercentMetric
|
||||
? formatUtils.formatPercent(metricData.industry_avg)
|
||||
: metricData.industry_avg?.toFixed(2) || '-'}
|
||||
<Text fontSize="xs" color={resolvedLabelColor} mt={1}>
|
||||
行业均值: {formattedAvg}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 期数选择器组件
|
||||
* 用于选择显示的财务报表期数,并提供刷新功能
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
HStack,
|
||||
Text,
|
||||
Select,
|
||||
IconButton,
|
||||
} from '@chakra-ui/react';
|
||||
import { RepeatIcon } from '@chakra-ui/icons';
|
||||
|
||||
export interface PeriodSelectorProps {
|
||||
/** 当前选中的期数 */
|
||||
selectedPeriods: number;
|
||||
/** 期数变更回调 */
|
||||
onPeriodsChange: (periods: number) => void;
|
||||
/** 刷新回调 */
|
||||
onRefresh: () => void;
|
||||
/** 是否加载中 */
|
||||
isLoading?: boolean;
|
||||
/** 可选期数列表,默认 [4, 8, 12, 16] */
|
||||
periodOptions?: number[];
|
||||
/** 标签文本 */
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const PeriodSelector: React.FC<PeriodSelectorProps> = memo(({
|
||||
selectedPeriods,
|
||||
onPeriodsChange,
|
||||
onRefresh,
|
||||
isLoading = false,
|
||||
periodOptions = [4, 8, 12, 16],
|
||||
label = '显示期数:',
|
||||
}) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardBody>
|
||||
<HStack justify="space-between">
|
||||
<HStack>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
{label}
|
||||
</Text>
|
||||
<Select
|
||||
value={selectedPeriods}
|
||||
onChange={(e) => onPeriodsChange(Number(e.target.value))}
|
||||
w="150px"
|
||||
size="sm"
|
||||
>
|
||||
{periodOptions.map((period) => (
|
||||
<option key={period} value={period}>
|
||||
最近{period}期
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</HStack>
|
||||
<IconButton
|
||||
icon={<RepeatIcon />}
|
||||
onClick={onRefresh}
|
||||
isLoading={isLoading}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
aria-label="刷新数据"
|
||||
/>
|
||||
</HStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
PeriodSelector.displayName = 'PeriodSelector';
|
||||
|
||||
export { PeriodSelector };
|
||||
export default PeriodSelector;
|
||||
@@ -1,11 +1,10 @@
|
||||
/**
|
||||
* 股票信息头部组件
|
||||
* 股票信息头部组件 - 黑金主题
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
Box,
|
||||
Grid,
|
||||
GridItem,
|
||||
VStack,
|
||||
@@ -18,93 +17,153 @@ import {
|
||||
StatNumber,
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Box,
|
||||
} from '@chakra-ui/react';
|
||||
import { formatUtils } from '@services/financialService';
|
||||
import type { StockInfoHeaderProps } from '../types';
|
||||
|
||||
// 黑金主题配置
|
||||
const darkGoldTheme = {
|
||||
bgCard: 'rgba(26, 32, 44, 0.95)',
|
||||
border: 'rgba(212, 175, 55, 0.3)',
|
||||
borderHover: 'rgba(212, 175, 55, 0.5)',
|
||||
gold: '#D4AF37',
|
||||
goldLight: '#F4D03F',
|
||||
orange: '#FF9500',
|
||||
red: '#FF4444',
|
||||
green: '#00C851',
|
||||
textPrimary: 'rgba(255, 255, 255, 0.92)',
|
||||
textSecondary: 'rgba(255, 255, 255, 0.7)',
|
||||
textMuted: 'rgba(255, 255, 255, 0.5)',
|
||||
tagBg: 'rgba(212, 175, 55, 0.15)',
|
||||
};
|
||||
|
||||
export const StockInfoHeader: React.FC<StockInfoHeaderProps> = ({
|
||||
stockInfo,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
}) => {
|
||||
if (!stockInfo) return null;
|
||||
|
||||
return (
|
||||
<Card mb={4}>
|
||||
<CardBody>
|
||||
<Grid templateColumns="repeat(6, 1fr)" gap={4}>
|
||||
<GridItem colSpan={{ base: 6, md: 2 }}>
|
||||
<VStack align="start">
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
股票名称
|
||||
</Text>
|
||||
<HStack>
|
||||
<Heading size="md">{stockInfo.stock_name}</Heading>
|
||||
<Badge>{stockInfo.stock_code}</Badge>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Stat>
|
||||
<StatLabel>最新EPS</StatLabel>
|
||||
<StatNumber>
|
||||
{stockInfo.key_metrics?.eps?.toFixed(3) || '-'}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Stat>
|
||||
<StatLabel>ROE</StatLabel>
|
||||
<StatNumber>
|
||||
{formatUtils.formatPercent(stockInfo.key_metrics?.roe)}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Stat>
|
||||
<StatLabel>营收增长</StatLabel>
|
||||
<StatNumber
|
||||
color={
|
||||
stockInfo.growth_rates?.revenue_growth
|
||||
? stockInfo.growth_rates.revenue_growth > 0
|
||||
? positiveColor
|
||||
: negativeColor
|
||||
: 'gray.500'
|
||||
}
|
||||
<Box
|
||||
mb={4}
|
||||
bg={darkGoldTheme.bgCard}
|
||||
border="1px solid"
|
||||
borderColor={darkGoldTheme.border}
|
||||
borderRadius="xl"
|
||||
p={5}
|
||||
boxShadow="0 4px 20px rgba(0, 0, 0, 0.3)"
|
||||
transition="all 0.3s ease"
|
||||
_hover={{
|
||||
borderColor: darkGoldTheme.borderHover,
|
||||
boxShadow: '0 8px 30px rgba(212, 175, 55, 0.15)',
|
||||
}}
|
||||
>
|
||||
<Grid templateColumns="repeat(6, 1fr)" gap={4} alignItems="center">
|
||||
<GridItem colSpan={{ base: 6, md: 2 }}>
|
||||
<VStack align="start" spacing={1}>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
股票名称
|
||||
</Text>
|
||||
<HStack>
|
||||
<Heading
|
||||
size="md"
|
||||
bgGradient={`linear(to-r, ${darkGoldTheme.gold}, ${darkGoldTheme.orange})`}
|
||||
bgClip="text"
|
||||
>
|
||||
{formatUtils.formatPercent(stockInfo.growth_rates?.revenue_growth)}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Stat>
|
||||
<StatLabel>利润增长</StatLabel>
|
||||
<StatNumber
|
||||
color={
|
||||
stockInfo.growth_rates?.profit_growth
|
||||
? stockInfo.growth_rates.profit_growth > 0
|
||||
? positiveColor
|
||||
: negativeColor
|
||||
: 'gray.500'
|
||||
}
|
||||
{stockInfo.stock_name}
|
||||
</Heading>
|
||||
<Badge
|
||||
bg={darkGoldTheme.tagBg}
|
||||
color={darkGoldTheme.gold}
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
py={1}
|
||||
borderRadius="md"
|
||||
>
|
||||
{formatUtils.formatPercent(stockInfo.growth_rates?.profit_growth)}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
{stockInfo.latest_forecast && (
|
||||
<Alert status="info" mt={4}>
|
||||
<AlertIcon />
|
||||
<Box>
|
||||
<Text fontWeight="bold">{stockInfo.latest_forecast.forecast_type}</Text>
|
||||
<Text fontSize="sm">{stockInfo.latest_forecast.content}</Text>
|
||||
</Box>
|
||||
</Alert>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
{stockInfo.stock_code}
|
||||
</Badge>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Stat>
|
||||
<StatLabel color={darkGoldTheme.textMuted} fontSize="xs">
|
||||
最新EPS
|
||||
</StatLabel>
|
||||
<StatNumber color={darkGoldTheme.goldLight} fontSize="lg">
|
||||
{stockInfo.key_metrics?.eps?.toFixed(3) || '-'}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Stat>
|
||||
<StatLabel color={darkGoldTheme.textMuted} fontSize="xs">
|
||||
ROE
|
||||
</StatLabel>
|
||||
<StatNumber color={darkGoldTheme.goldLight} fontSize="lg">
|
||||
{formatUtils.formatPercent(stockInfo.key_metrics?.roe)}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Stat>
|
||||
<StatLabel color={darkGoldTheme.textMuted} fontSize="xs">
|
||||
营收增长
|
||||
</StatLabel>
|
||||
<StatNumber
|
||||
fontSize="lg"
|
||||
color={
|
||||
stockInfo.growth_rates?.revenue_growth
|
||||
? stockInfo.growth_rates.revenue_growth > 0
|
||||
? darkGoldTheme.red
|
||||
: darkGoldTheme.green
|
||||
: darkGoldTheme.textMuted
|
||||
}
|
||||
>
|
||||
{formatUtils.formatPercent(stockInfo.growth_rates?.revenue_growth)}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Stat>
|
||||
<StatLabel color={darkGoldTheme.textMuted} fontSize="xs">
|
||||
利润增长
|
||||
</StatLabel>
|
||||
<StatNumber
|
||||
fontSize="lg"
|
||||
color={
|
||||
stockInfo.growth_rates?.profit_growth
|
||||
? stockInfo.growth_rates.profit_growth > 0
|
||||
? darkGoldTheme.red
|
||||
: darkGoldTheme.green
|
||||
: darkGoldTheme.textMuted
|
||||
}
|
||||
>
|
||||
{formatUtils.formatPercent(stockInfo.growth_rates?.profit_growth)}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
{stockInfo.latest_forecast && (
|
||||
<Alert
|
||||
status="info"
|
||||
mt={4}
|
||||
bg="rgba(212, 175, 55, 0.1)"
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor={darkGoldTheme.border}
|
||||
>
|
||||
<AlertIcon color={darkGoldTheme.gold} />
|
||||
<Box>
|
||||
<Text fontWeight="bold" color={darkGoldTheme.gold}>
|
||||
{stockInfo.latest_forecast.forecast_type}
|
||||
</Text>
|
||||
<Text fontSize="sm" color={darkGoldTheme.textSecondary}>
|
||||
{stockInfo.latest_forecast.content}
|
||||
</Text>
|
||||
</Box>
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* 组件统一导出
|
||||
*/
|
||||
|
||||
export { PeriodSelector } from './PeriodSelector';
|
||||
export { StockInfoHeader } from './StockInfoHeader';
|
||||
export { BalanceSheetTable } from './BalanceSheetTable';
|
||||
export { IncomeStatementTable } from './IncomeStatementTable';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* 财务全景组件
|
||||
* 重构后的主组件,使用模块化结构
|
||||
* 重构后的主组件,使用模块化结构和 SubTabContainer 二级导航
|
||||
*/
|
||||
|
||||
import React, { useState, ReactNode } from 'react';
|
||||
import React, { useState, useMemo, ReactNode } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
@@ -11,20 +11,12 @@ import {
|
||||
HStack,
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Heading,
|
||||
Text,
|
||||
Badge,
|
||||
Select,
|
||||
IconButton,
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Skeleton,
|
||||
Tabs,
|
||||
TabList,
|
||||
TabPanels,
|
||||
Tab,
|
||||
TabPanel,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
@@ -40,25 +32,25 @@ import {
|
||||
Td,
|
||||
TableContainer,
|
||||
Divider,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import { RepeatIcon } from '@chakra-ui/icons';
|
||||
import { BarChart3, DollarSign, TrendingUp } from 'lucide-react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import { formatUtils } from '@services/financialService';
|
||||
|
||||
// 通用组件
|
||||
import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer';
|
||||
|
||||
// 内部模块导入
|
||||
import { useFinancialData } from './hooks';
|
||||
import { COLORS } from './constants';
|
||||
import { calculateYoYChange, getCellBackground, getMetricChartOption } from './utils';
|
||||
import {
|
||||
StockInfoHeader,
|
||||
BalanceSheetTable,
|
||||
IncomeStatementTable,
|
||||
CashflowTable,
|
||||
FinancialMetricsTable,
|
||||
MainBusinessAnalysis,
|
||||
ComparisonAnalysis,
|
||||
} from './components';
|
||||
import { BalanceSheetTab, IncomeStatementTab, CashflowTab } from './tabs';
|
||||
import type { FinancialPanoramaProps } from './types';
|
||||
|
||||
/**
|
||||
@@ -73,29 +65,24 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
||||
cashflow,
|
||||
financialMetrics,
|
||||
mainBusiness,
|
||||
forecast,
|
||||
industryRank,
|
||||
comparison,
|
||||
loading,
|
||||
error,
|
||||
refetch,
|
||||
currentStockCode,
|
||||
selectedPeriods,
|
||||
setSelectedPeriods,
|
||||
} = useFinancialData({ stockCode: propStockCode });
|
||||
|
||||
// UI 状态
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [modalContent, setModalContent] = useState<ReactNode>(null);
|
||||
|
||||
// 颜色配置
|
||||
const { bgColor, hoverBg, positiveColor, negativeColor, borderColor } = COLORS;
|
||||
const { bgColor, hoverBg, positiveColor, negativeColor } = COLORS;
|
||||
|
||||
// 点击指标行显示图表
|
||||
const showMetricChart = (
|
||||
metricName: string,
|
||||
metricKey: string,
|
||||
_metricKey: string,
|
||||
data: Array<{ period: string; [key: string]: unknown }>,
|
||||
dataPath: string
|
||||
) => {
|
||||
@@ -204,6 +191,45 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
// Tab 配置 - 只保留三大财务报表
|
||||
const tabConfigs: SubTabConfig[] = useMemo(
|
||||
() => [
|
||||
{ key: 'balance', name: '资产负债表', icon: BarChart3, component: BalanceSheetTab },
|
||||
{ key: 'income', name: '利润表', icon: DollarSign, component: IncomeStatementTab },
|
||||
{ key: 'cashflow', name: '现金流量表', icon: TrendingUp, component: CashflowTab },
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
// 传递给 Tab 组件的 props
|
||||
const componentProps = useMemo(
|
||||
() => ({
|
||||
// 数据
|
||||
balanceSheet,
|
||||
incomeStatement,
|
||||
cashflow,
|
||||
// 工具函数
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
// 颜色配置
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}),
|
||||
[
|
||||
balanceSheet,
|
||||
incomeStatement,
|
||||
cashflow,
|
||||
showMetricChart,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<Container maxW="container.xl" py={5}>
|
||||
<VStack spacing={6} align="stretch">
|
||||
@@ -250,124 +276,35 @@ const FinancialPanorama: React.FC<FinancialPanoramaProps> = ({ stockCode: propSt
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 主要内容区域 */}
|
||||
{/* 财务指标速览 */}
|
||||
{!loading && stockInfo && (
|
||||
<Tabs
|
||||
index={activeTab}
|
||||
onChange={setActiveTab}
|
||||
variant="enclosed"
|
||||
colorScheme="blue"
|
||||
>
|
||||
<TabList>
|
||||
<Tab>财务概览</Tab>
|
||||
<Tab>资产负债表</Tab>
|
||||
<Tab>利润表</Tab>
|
||||
<Tab>现金流量表</Tab>
|
||||
<Tab>财务指标</Tab>
|
||||
<Tab>主营业务</Tab>
|
||||
</TabList>
|
||||
<FinancialMetricsTable data={financialMetrics} {...tableProps} />
|
||||
)}
|
||||
|
||||
<TabPanels>
|
||||
{/* 财务概览 */}
|
||||
<TabPanel>
|
||||
<VStack spacing={4} align="stretch">
|
||||
<ComparisonAnalysis comparison={comparison} />
|
||||
<FinancialMetricsTable data={financialMetrics} {...tableProps} />
|
||||
</VStack>
|
||||
</TabPanel>
|
||||
{/* 主营业务 */}
|
||||
{!loading && stockInfo && (
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Text fontSize="lg" fontWeight="bold" mb={4}>
|
||||
主营业务
|
||||
</Text>
|
||||
<MainBusinessAnalysis mainBusiness={mainBusiness} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 资产负债表 */}
|
||||
<TabPanel>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<HStack justify="space-between">
|
||||
<Heading size="md">资产负债表</Heading>
|
||||
<HStack spacing={2}>
|
||||
<Badge colorScheme="blue">
|
||||
显示最近{Math.min(balanceSheet.length, 8)}期
|
||||
</Badge>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
红涨绿跌 | 同比变化
|
||||
</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
提示:表格可横向滚动查看更多数据,点击行查看历史趋势
|
||||
</Text>
|
||||
</VStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<BalanceSheetTable data={balanceSheet} {...tableProps} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</TabPanel>
|
||||
|
||||
{/* 利润表 */}
|
||||
<TabPanel>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<HStack justify="space-between">
|
||||
<Heading size="md">利润表</Heading>
|
||||
<HStack spacing={2}>
|
||||
<Badge colorScheme="blue">
|
||||
显示最近{Math.min(incomeStatement.length, 8)}期
|
||||
</Badge>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
红涨绿跌 | 同比变化
|
||||
</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
提示:Q1、中报、Q3、年报数据为累计值,同比显示与去年同期对比
|
||||
</Text>
|
||||
</VStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<IncomeStatementTable data={incomeStatement} {...tableProps} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</TabPanel>
|
||||
|
||||
{/* 现金流量表 */}
|
||||
<TabPanel>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<HStack justify="space-between">
|
||||
<Heading size="md">现金流量表</Heading>
|
||||
<HStack spacing={2}>
|
||||
<Badge colorScheme="blue">
|
||||
显示最近{Math.min(cashflow.length, 8)}期
|
||||
</Badge>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
红涨绿跌 | 同比变化
|
||||
</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
提示:现金流数据为累计值,正值红色表示现金流入,负值绿色表示现金流出
|
||||
</Text>
|
||||
</VStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<CashflowTable data={cashflow} {...tableProps} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</TabPanel>
|
||||
|
||||
{/* 财务指标 */}
|
||||
<TabPanel>
|
||||
<FinancialMetricsTable data={financialMetrics} {...tableProps} />
|
||||
</TabPanel>
|
||||
|
||||
{/* 主营业务 */}
|
||||
<TabPanel>
|
||||
<MainBusinessAnalysis mainBusiness={mainBusiness} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
{/* 三大财务报表 - 使用 SubTabContainer 二级导航 */}
|
||||
{!loading && stockInfo && (
|
||||
<Card bg="gray.900" shadow="md" border="1px solid" borderColor="rgba(212, 175, 55, 0.3)">
|
||||
<CardBody p={0}>
|
||||
<SubTabContainer
|
||||
tabs={tabConfigs}
|
||||
componentProps={componentProps}
|
||||
themePreset="blackGold"
|
||||
isLazy
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 错误提示 */}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 资产负债表 Tab
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
VStack,
|
||||
HStack,
|
||||
Heading,
|
||||
Badge,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { BalanceSheetTable } from '../components';
|
||||
import type { BalanceSheetData } from '../types';
|
||||
|
||||
export interface BalanceSheetTabProps {
|
||||
balanceSheet: BalanceSheetData[];
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
const BalanceSheetTab: React.FC<BalanceSheetTabProps> = ({
|
||||
balanceSheet,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}) => {
|
||||
const tableProps = {
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<HStack justify="space-between">
|
||||
<Heading size="md">资产负债表</Heading>
|
||||
<HStack spacing={2}>
|
||||
<Badge colorScheme="blue">
|
||||
显示最近{Math.min(balanceSheet.length, 8)}期
|
||||
</Badge>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
红涨绿跌 | 同比变化
|
||||
</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
提示:表格可横向滚动查看更多数据,点击行查看历史趋势
|
||||
</Text>
|
||||
</VStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<BalanceSheetTable data={balanceSheet} {...tableProps} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default BalanceSheetTab;
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 现金流量表 Tab
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
VStack,
|
||||
HStack,
|
||||
Heading,
|
||||
Badge,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { CashflowTable } from '../components';
|
||||
import type { CashflowData } from '../types';
|
||||
|
||||
export interface CashflowTabProps {
|
||||
cashflow: CashflowData[];
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
const CashflowTab: React.FC<CashflowTabProps> = ({
|
||||
cashflow,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}) => {
|
||||
const tableProps = {
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<HStack justify="space-between">
|
||||
<Heading size="md">现金流量表</Heading>
|
||||
<HStack spacing={2}>
|
||||
<Badge colorScheme="blue">
|
||||
显示最近{Math.min(cashflow.length, 8)}期
|
||||
</Badge>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
红涨绿跌 | 同比变化
|
||||
</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
提示:现金流数据为累计值,正值红色表示现金流入,负值绿色表示现金流出
|
||||
</Text>
|
||||
</VStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<CashflowTable data={cashflow} {...tableProps} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CashflowTab;
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 利润表 Tab
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
VStack,
|
||||
HStack,
|
||||
Heading,
|
||||
Badge,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { IncomeStatementTable } from '../components';
|
||||
import type { IncomeStatementData } from '../types';
|
||||
|
||||
export interface IncomeStatementTabProps {
|
||||
incomeStatement: IncomeStatementData[];
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
const IncomeStatementTab: React.FC<IncomeStatementTabProps> = ({
|
||||
incomeStatement,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}) => {
|
||||
const tableProps = {
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<HStack justify="space-between">
|
||||
<Heading size="md">利润表</Heading>
|
||||
<HStack spacing={2}>
|
||||
<Badge colorScheme="blue">
|
||||
显示最近{Math.min(incomeStatement.length, 8)}期
|
||||
</Badge>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
红涨绿跌 | 同比变化
|
||||
</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
提示:Q1、中报、Q3、年报数据为累计值,同比显示与去年同期对比
|
||||
</Text>
|
||||
</VStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<IncomeStatementTable data={incomeStatement} {...tableProps} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default IncomeStatementTab;
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 主营业务 Tab
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MainBusinessAnalysis } from '../components';
|
||||
import type { MainBusinessData } from '../types';
|
||||
|
||||
export interface MainBusinessTabProps {
|
||||
mainBusiness: MainBusinessData | null;
|
||||
}
|
||||
|
||||
const MainBusinessTab: React.FC<MainBusinessTabProps> = ({ mainBusiness }) => {
|
||||
return <MainBusinessAnalysis mainBusiness={mainBusiness} />;
|
||||
};
|
||||
|
||||
export default MainBusinessTab;
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 财务指标 Tab
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FinancialMetricsTable } from '../components';
|
||||
import type { FinancialMetricsData } from '../types';
|
||||
|
||||
export interface MetricsTabProps {
|
||||
financialMetrics: FinancialMetricsData[];
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
const MetricsTab: React.FC<MetricsTabProps> = ({
|
||||
financialMetrics,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}) => {
|
||||
const tableProps = {
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
return <FinancialMetricsTable data={financialMetrics} {...tableProps} />;
|
||||
};
|
||||
|
||||
export default MetricsTab;
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 财务概览 Tab
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { VStack } from '@chakra-ui/react';
|
||||
import { ComparisonAnalysis, FinancialMetricsTable } from '../components';
|
||||
import type { FinancialMetricsData, ComparisonData } from '../types';
|
||||
|
||||
export interface OverviewTabProps {
|
||||
comparison: ComparisonData[];
|
||||
financialMetrics: FinancialMetricsData[];
|
||||
showMetricChart: (name: string, key: string, data: unknown[], path: string) => void;
|
||||
calculateYoYChange: (value: number, period: string, data: unknown[], path: string) => { change: number; intensity: number };
|
||||
getCellBackground: (change: number, intensity: number) => string;
|
||||
positiveColor: string;
|
||||
negativeColor: string;
|
||||
bgColor: string;
|
||||
hoverBg: string;
|
||||
}
|
||||
|
||||
const OverviewTab: React.FC<OverviewTabProps> = ({
|
||||
comparison,
|
||||
financialMetrics,
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
}) => {
|
||||
const tableProps = {
|
||||
showMetricChart,
|
||||
calculateYoYChange,
|
||||
getCellBackground,
|
||||
positiveColor,
|
||||
negativeColor,
|
||||
bgColor,
|
||||
hoverBg,
|
||||
};
|
||||
|
||||
return (
|
||||
<VStack spacing={4} align="stretch">
|
||||
<ComparisonAnalysis comparison={comparison} />
|
||||
<FinancialMetricsTable data={financialMetrics} {...tableProps} />
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default OverviewTab;
|
||||
12
src/views/Company/components/FinancialPanorama/tabs/index.ts
Normal file
12
src/views/Company/components/FinancialPanorama/tabs/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Tab 组件统一导出
|
||||
* 仅保留三大财务报表 Tab
|
||||
*/
|
||||
|
||||
export { default as BalanceSheetTab } from './BalanceSheetTab';
|
||||
export { default as IncomeStatementTab } from './IncomeStatementTab';
|
||||
export { default as CashflowTab } from './CashflowTab';
|
||||
|
||||
export type { BalanceSheetTabProps } from './BalanceSheetTab';
|
||||
export type { IncomeStatementTabProps } from './IncomeStatementTab';
|
||||
export type { CashflowTabProps } from './CashflowTab';
|
||||
@@ -392,8 +392,10 @@ export interface MainBusinessAnalysisProps {
|
||||
/** 行业排名 Props */
|
||||
export interface IndustryRankingViewProps {
|
||||
industryRank: IndustryRankData[];
|
||||
bgColor: string;
|
||||
borderColor: string;
|
||||
bgColor?: string;
|
||||
borderColor?: string;
|
||||
textColor?: string;
|
||||
labelColor?: string;
|
||||
}
|
||||
|
||||
/** 股票对比 Props */
|
||||
|
||||
Reference in New Issue
Block a user