refactor: 抽取通用 Tab 容器组件,重构 BasicInfoTab 和 DeepAnalysisTab

新增组件:
- TabPanelContainer: 三级容器,统一 loading 状态 + VStack 布局 + 免责声明
- SubTabContainer: 二级导航容器,支持黑金/默认主题预设

重构:
- BasicInfoTab: 使用 SubTabContainer 替代原有 Tabs 实现
- DeepAnalysisTab: 拆分为 4 个子 Tab(战略分析/业务结构/产业链/发展历程)
- TabContainer: 样式调整,与 SubTabContainer 保持一致

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-12 11:55:50 +08:00
parent 96fe919164
commit b8cd520014
15 changed files with 572 additions and 234 deletions

View File

@@ -0,0 +1,195 @@
/**
* SubTabContainer - 二级导航容器组件
*
* 用于模块内的子功能切换(如公司档案下的股权结构、管理团队等)
* 与 TabContainer一级导航区分无 Card 包裹,直接融入父容器
*
* @example
* ```tsx
* <SubTabContainer
* tabs={[
* { key: 'tab1', name: 'Tab 1', icon: FaHome, component: Tab1 },
* { key: 'tab2', name: 'Tab 2', icon: FaUser, component: Tab2 },
* ]}
* componentProps={{ stockCode: '000001' }}
* onTabChange={(index, key) => console.log('切换到', key)}
* />
* ```
*/
import React, { useState, useCallback } from 'react';
import {
Box,
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
Icon,
HStack,
Text,
} from '@chakra-ui/react';
import type { ComponentType } from 'react';
import type { IconType } from 'react-icons';
/**
* Tab 配置项
*/
export interface SubTabConfig {
key: string;
name: string;
icon?: IconType | ComponentType;
component?: ComponentType<any>;
}
/**
* 主题配置
*/
export interface SubTabTheme {
bg: string;
borderColor: string;
tabSelectedBg: string;
tabSelectedColor: string;
tabUnselectedColor: string;
tabHoverBg: string;
}
/**
* 预设主题
*/
const THEME_PRESETS: Record<string, SubTabTheme> = {
blackGold: {
bg: 'gray.900',
borderColor: 'rgba(212, 175, 55, 0.3)',
tabSelectedBg: '#D4AF37',
tabSelectedColor: 'gray.900',
tabUnselectedColor: '#D4AF37',
tabHoverBg: 'gray.600',
},
default: {
bg: 'white',
borderColor: 'gray.200',
tabSelectedBg: 'blue.500',
tabSelectedColor: 'white',
tabUnselectedColor: 'gray.600',
tabHoverBg: 'gray.100',
},
};
export interface SubTabContainerProps {
/** Tab 配置数组 */
tabs: SubTabConfig[];
/** 传递给 Tab 内容组件的 props */
componentProps?: Record<string, any>;
/** 默认选中的 Tab 索引 */
defaultIndex?: number;
/** 受控模式下的当前索引 */
index?: number;
/** Tab 变更回调 */
onTabChange?: (index: number, tabKey: string) => void;
/** 主题预设 */
themePreset?: 'blackGold' | 'default';
/** 自定义主题(优先级高于预设) */
theme?: Partial<SubTabTheme>;
/** 内容区内边距 */
contentPadding?: number;
/** 是否懒加载 */
isLazy?: boolean;
}
const SubTabContainer: React.FC<SubTabContainerProps> = ({
tabs,
componentProps = {},
defaultIndex = 0,
index: controlledIndex,
onTabChange,
themePreset = 'blackGold',
theme: customTheme,
contentPadding = 4,
isLazy = true,
}) => {
// 内部状态(非受控模式)
const [internalIndex, setInternalIndex] = useState(defaultIndex);
// 当前索引
const currentIndex = controlledIndex ?? internalIndex;
// 合并主题
const theme: SubTabTheme = {
...THEME_PRESETS[themePreset],
...customTheme,
};
/**
* 处理 Tab 切换
*/
const handleTabChange = useCallback(
(newIndex: number) => {
const tabKey = tabs[newIndex]?.key || '';
onTabChange?.(newIndex, tabKey);
if (controlledIndex === undefined) {
setInternalIndex(newIndex);
}
},
[tabs, onTabChange, controlledIndex]
);
return (
<Box>
<Tabs
isLazy={isLazy}
variant="unstyled"
index={currentIndex}
onChange={handleTabChange}
>
<TabList
bg={theme.bg}
borderBottom="1px solid"
borderColor={theme.borderColor}
px={4}
py={2}
flexWrap="wrap"
gap={2}
>
{tabs.map((tab) => (
<Tab
key={tab.key}
color={theme.tabUnselectedColor}
borderRadius="full"
px={4}
py={2}
fontSize="sm"
_selected={{
bg: theme.tabSelectedBg,
color: theme.tabSelectedColor,
fontWeight: 'bold',
}}
_hover={{
bg: theme.tabHoverBg,
}}
>
<HStack spacing={2}>
{tab.icon && <Icon as={tab.icon} boxSize={4} />}
<Text>{tab.name}</Text>
</HStack>
</Tab>
))}
</TabList>
<TabPanels p={contentPadding}>
{tabs.map((tab) => {
const Component = tab.component;
return (
<TabPanel key={tab.key} p={0}>
{Component ? <Component {...componentProps} /> : null}
</TabPanel>
);
})}
</TabPanels>
</Tabs>
</Box>
);
};
export default SubTabContainer;

View File

@@ -15,30 +15,36 @@ const TabNavigation: React.FC<TabNavigationProps> = ({
}) => {
return (
<TabList
py={4}
bg={themeColors.bg}
borderBottom="1px solid"
borderColor={themeColors.dividerColor}
borderTopLeftRadius={borderRadius}
borderTopRightRadius={borderRadius}
px={4}
py={2}
flexWrap="wrap"
gap={2}
>
{tabs.map((tab, index) => (
{tabs.map((tab) => (
<Tab
key={tab.key}
color={themeColors.unselectedText}
borderRadius="full"
px={4}
py={2}
fontSize="sm"
_selected={{
bg: themeColors.selectedBg,
color: themeColors.selectedText,
fontWeight: 'bold',
}}
_hover={{
color: themeColors.selectedText,
bg: 'whiteAlpha.100',
}}
mr={index < tabs.length - 1 ? 2 : 0}
>
<HStack spacing={2}>
{tab.icon && <Icon as={tab.icon} boxSize="18px" />}
<Text fontSize="15px">{tab.name}</Text>
{tab.icon && <Icon as={tab.icon} boxSize={4} />}
<Text>{tab.name}</Text>
</HStack>
</Tab>
))}

View File

@@ -34,7 +34,6 @@ import {
Tabs,
TabPanels,
TabPanel,
Divider,
} from '@chakra-ui/react';
import TabNavigation from './TabNavigation';
@@ -113,8 +112,7 @@ const TabContainer: React.FC<TabContainerProps> = ({
<CardBody p={0}>
<Tabs
isLazy={isLazy}
variant="soft-rounded"
colorScheme="blue"
variant="unstyled"
size={size}
index={currentIndex}
onChange={handleTabChange}
@@ -126,9 +124,6 @@ const TabContainer: React.FC<TabContainerProps> = ({
borderRadius={borderRadius}
/>
{/* 分割线 */}
{showDivider && <Divider borderColor={themeColors.dividerColor} />}
{/* Tab 内容面板 */}
<TabPanels>{renderTabPanels()}</TabPanels>
</Tabs>

View File

@@ -0,0 +1,100 @@
/**
* TabPanelContainer - Tab 面板通用容器组件
*
* 提供统一的:
* - Loading 状态处理
* - VStack 布局
* - 免责声明(可选)
*
* @example
* ```tsx
* <TabPanelContainer loading={loading} showDisclaimer>
* <YourContent />
* </TabPanelContainer>
* ```
*/
import React, { memo } from 'react';
import { VStack, Center, Spinner, Text, Box } from '@chakra-ui/react';
// 默认免责声明文案
const DEFAULT_DISCLAIMER =
'免责声明本内容由AI模型基于新闻、公告、研报等公开信息自动分析和生成未经许可严禁转载。所有内容仅供参考不构成任何投资建议请投资者注意风险独立审慎决策。';
export interface TabPanelContainerProps {
/** 是否处于加载状态 */
loading?: boolean;
/** 加载状态显示的文案 */
loadingMessage?: string;
/** 加载状态高度 */
loadingHeight?: string;
/** 子组件间距,默认 6 */
spacing?: number;
/** 内边距,默认 4 */
padding?: number;
/** 是否显示免责声明,默认 false */
showDisclaimer?: boolean;
/** 自定义免责声明文案 */
disclaimerText?: string;
/** 子组件 */
children: React.ReactNode;
}
/**
* 加载状态组件
*/
const LoadingState: React.FC<{ message: string; height: string }> = ({
message,
height,
}) => (
<Center h={height}>
<VStack spacing={3}>
<Spinner size="lg" color="blue.500" thickness="3px" />
<Text fontSize="sm" color="gray.500">
{message}
</Text>
</VStack>
</Center>
);
/**
* 免责声明组件
*/
const DisclaimerText: React.FC<{ text: string }> = ({ text }) => (
<Text mt={4} color="gray.500" fontSize="12px" lineHeight="1.5">
{text}
</Text>
);
/**
* Tab 面板通用容器
*/
const TabPanelContainer: React.FC<TabPanelContainerProps> = memo(
({
loading = false,
loadingMessage = '加载中...',
loadingHeight = '200px',
spacing = 6,
padding = 4,
showDisclaimer = false,
disclaimerText = DEFAULT_DISCLAIMER,
children,
}) => {
if (loading) {
return <LoadingState message={loadingMessage} height={loadingHeight} />;
}
return (
<Box p={padding}>
<VStack spacing={spacing} align="stretch">
{children}
</VStack>
{showDisclaimer && <DisclaimerText text={disclaimerText} />}
</Box>
);
}
);
TabPanelContainer.displayName = 'TabPanelContainer';
export default TabPanelContainer;

View File

@@ -10,7 +10,7 @@ import {
ConcentrationCard,
ShareholdersTable,
} from "../../components/shareholder";
import TabPanelContainer from "./TabPanelContainer";
import TabPanelContainer from "@components/TabPanelContainer";
interface ShareholderPanelProps {
stockCode: string;

View File

@@ -1,56 +0,0 @@
/**
* Tab 面板通用容器组件
*
* 提供统一的 loading 状态处理和布局包裹
* 用于 ShareholderPanel、ManagementPanel 等 Tab 面板
*/
import React, { memo } from 'react';
import { VStack } from '@chakra-ui/react';
import LoadingState from './LoadingState';
interface TabPanelContainerProps {
/** 是否处于加载状态 */
loading?: boolean;
/** 加载状态显示的文案 */
loadingMessage?: string;
/** 子组件间距,默认 6 */
spacing?: number;
/** 子组件 */
children: React.ReactNode;
}
/**
* Tab 面板通用容器
*
* 功能:
* 1. 统一处理 loading 状态,显示 LoadingState 组件
* 2. 提供 VStack 布局包裹,统一 spacing 和 align
*
* @example
* ```tsx
* <TabPanelContainer loading={loading} loadingMessage="加载数据...">
* <YourContent />
* </TabPanelContainer>
* ```
*/
const TabPanelContainer: React.FC<TabPanelContainerProps> = memo(({
loading = false,
loadingMessage = '加载中...',
spacing = 6,
children,
}) => {
if (loading) {
return <LoadingState message={loadingMessage} />;
}
return (
<VStack spacing={spacing} align="stretch">
{children}
</VStack>
);
});
TabPanelContainer.displayName = 'TabPanelContainer';
export default TabPanelContainer;

View File

@@ -2,7 +2,8 @@
// 组件导出
export { default as LoadingState } from "./LoadingState";
export { default as TabPanelContainer } from "./TabPanelContainer";
// TabPanelContainer 已提升为通用组件,从 @components/TabPanelContainer 导入
export { default as TabPanelContainer } from "@components/TabPanelContainer";
export { default as ShareholderPanel } from "./ShareholderPanel";
export { ManagementPanel } from "./management";
export { default as AnnouncementsPanel } from "./AnnouncementsPanel";

View File

@@ -11,7 +11,7 @@ import {
import { useManagementData } from "../../../hooks/useManagementData";
import { THEME } from "../../config";
import TabPanelContainer from "../TabPanelContainer";
import TabPanelContainer from "@components/TabPanelContainer";
import CategorySection from "./CategorySection";
import type {
ManagementPerson,

View File

@@ -1,21 +1,11 @@
// src/views/Company/components/CompanyOverview/BasicInfoTab/index.tsx
// 基本信息 Tab 组件 - 可配置版本(黑金主题)
// 基本信息 Tab 组件 - 使用 SubTabContainer 通用组件
import React from "react";
import {
Card,
CardBody,
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
Icon,
HStack,
Text,
} from "@chakra-ui/react";
import React, { useMemo } from "react";
import { Card, CardBody } from "@chakra-ui/react";
import SubTabContainer, { type SubTabConfig } from "@components/SubTabContainer";
import { THEME, TAB_CONFIG, getEnabledTabs, type TabConfig } from "./config";
import { THEME, TAB_CONFIG, getEnabledTabs } from "./config";
import {
ShareholderPanel,
ManagementPanel,
@@ -44,13 +34,27 @@ const TAB_COMPONENTS: Record<string, React.FC<any>> = {
business: BusinessInfoPanel,
};
/**
* 构建 SubTabContainer 所需的 tabs 配置
*/
const buildTabsConfig = (enabledKeys?: string[]): SubTabConfig[] => {
const enabledTabs = getEnabledTabs(enabledKeys);
return enabledTabs.map((tab) => ({
key: tab.key,
name: tab.name,
icon: tab.icon,
component: TAB_COMPONENTS[tab.key],
}));
};
/**
* 基本信息 Tab 组件
*
* 特性:
* - 使用 SubTabContainer 通用组件
* - 可配置显示哪些 TabenabledTabs
* - 黑金主题
* - 懒加载isLazy
* - 懒加载
* - 支持 Tab 变更回调
*/
const BasicInfoTab: React.FC<BasicInfoTabProps> = ({
@@ -60,79 +64,19 @@ const BasicInfoTab: React.FC<BasicInfoTabProps> = ({
defaultTabIndex = 0,
onTabChange,
}) => {
// 获取启用的 Tab 配置
const tabs = getEnabledTabs(enabledTabs);
// 处理 Tab 变更
const handleTabChange = (index: number) => {
if (onTabChange && tabs[index]) {
onTabChange(index, tabs[index].key);
}
};
// 渲染单个 Tab 内容
const renderTabContent = (tab: TabConfig) => {
const Component = TAB_COMPONENTS[tab.key];
if (!Component) return null;
// business Tab 需要 basicInfo其他需要 stockCode
if (tab.key === "business") {
return <Component basicInfo={basicInfo} />;
}
return <Component stockCode={stockCode} />;
};
// 构建 tabs 配置(缓存避免重复计算)
const tabs = useMemo(() => buildTabsConfig(enabledTabs), [enabledTabs]);
return (
<Card bg={THEME.cardBg} shadow="md" border="1px solid" borderColor={THEME.border}>
<CardBody p={0}>
<Tabs
isLazy
variant="unstyled"
<SubTabContainer
tabs={tabs}
componentProps={{ stockCode, basicInfo }}
defaultIndex={defaultTabIndex}
onChange={handleTabChange}
>
<TabList
bg={THEME.bg}
borderBottom="1px solid"
borderColor={THEME.border}
px={4}
py={2}
flexWrap="wrap"
gap={2}
>
{tabs.map((tab) => (
<Tab
key={tab.key}
color={THEME.tabUnselected.color}
borderRadius="full"
px={4}
py={2}
fontSize="sm"
_selected={{
bg: THEME.tabSelected.bg,
color: THEME.tabSelected.color,
fontWeight: "bold",
}}
_hover={{
bg: THEME.tableHoverBg,
}}
>
<HStack spacing={2}>
<Icon as={tab.icon} boxSize={4} />
<Text>{tab.name}</Text>
</HStack>
</Tab>
))}
</TabList>
<TabPanels p={4}>
{tabs.map((tab) => (
<TabPanel key={tab.key} p={0}>
{renderTabContent(tab)}
</TabPanel>
))}
</TabPanels>
</Tabs>
onTabChange={onTabChange}
themePreset="blackGold"
/>
</CardBody>
</Card>
);

View File

@@ -1,23 +1,36 @@
/**
* 深度分析 Tab 主组件
*
* 组合所有子组件,显示公司深度分析内容
* 使用 SubTabContainer 二级导航组件,分为 4 个子 Tab
* 1. 战略分析 - 核心定位 + 战略分析 + 竞争地位
* 2. 业务结构 - 业务结构树 + 业务板块详情
* 3. 产业链 - 产业链分析(独立,含 Sankey 图)
* 4. 发展历程 - 关键因素 + 时间线
*/
import React from 'react';
import { VStack, Center, Text, Spinner, Grid, GridItem } from '@chakra-ui/react';
import {
CorePositioningCard,
CompetitiveAnalysisCard,
BusinessStructureCard,
ValueChainCard,
KeyFactorsCard,
TimelineCard,
BusinessSegmentsCard,
StrategyAnalysisCard,
} from './components';
import { Card, CardBody, Center, VStack, Spinner, Text } from '@chakra-ui/react';
import { FaBrain, FaBuilding, FaLink, FaHistory } from 'react-icons/fa';
import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer';
import { StrategyTab, BusinessTab, ValueChainTab, DevelopmentTab } from './tabs';
import type { DeepAnalysisTabProps } from './types';
// 主题配置(与 BasicInfoTab 保持一致)
const THEME = {
cardBg: 'gray.900',
border: 'rgba(212, 175, 55, 0.3)',
};
/**
* Tab 配置
*/
const DEEP_ANALYSIS_TABS: SubTabConfig[] = [
{ key: 'strategy', name: '战略分析', icon: FaBrain, component: StrategyTab },
{ key: 'business', name: '业务结构', icon: FaBuilding, component: BusinessTab },
{ key: 'valueChain', name: '产业链', icon: FaLink, component: ValueChainTab },
{ key: 'development', name: '发展历程', icon: FaHistory, component: DevelopmentTab },
];
const DeepAnalysisTab: React.FC<DeepAnalysisTabProps> = ({
comprehensiveData,
valueChainData,
@@ -40,74 +53,22 @@ const DeepAnalysisTab: React.FC<DeepAnalysisTabProps> = ({
}
return (
<VStack spacing={6} align="stretch">
{/* 核心定位卡片 */}
{comprehensiveData?.qualitative_analysis && (
<CorePositioningCard
qualitativeAnalysis={comprehensiveData.qualitative_analysis}
cardBg={cardBg}
<Card bg={THEME.cardBg} shadow="md" border="1px solid" borderColor={THEME.border}>
<CardBody p={0}>
<SubTabContainer
tabs={DEEP_ANALYSIS_TABS}
componentProps={{
comprehensiveData,
valueChainData,
keyFactorsData,
cardBg,
expandedSegments,
onToggleSegment,
}}
themePreset="blackGold"
/>
)}
{/* 战略分析 */}
{comprehensiveData?.qualitative_analysis?.strategy && (
<StrategyAnalysisCard
strategy={comprehensiveData.qualitative_analysis.strategy}
cardBg={cardBg}
/>
)}
{/* 竞争地位分析 */}
{comprehensiveData?.competitive_position && (
<CompetitiveAnalysisCard comprehensiveData={comprehensiveData} />
)}
{/* 业务结构分析 */}
{comprehensiveData?.business_structure &&
comprehensiveData.business_structure.length > 0 && (
<BusinessStructureCard
businessStructure={comprehensiveData.business_structure}
cardBg={cardBg}
/>
)}
{/* 业务板块详情 */}
{comprehensiveData?.business_segments &&
comprehensiveData.business_segments.length > 0 && (
<BusinessSegmentsCard
businessSegments={comprehensiveData.business_segments}
expandedSegments={expandedSegments}
onToggleSegment={onToggleSegment}
cardBg={cardBg}
/>
)}
{/* 产业链分析 */}
{valueChainData && (
<ValueChainCard valueChainData={valueChainData} cardBg={cardBg} />
)}
{/* 关键因素与发展时间线 */}
<Grid templateColumns="repeat(2, 1fr)" gap={6}>
<GridItem colSpan={{ base: 2, lg: 1 }}>
{keyFactorsData?.key_factors && (
<KeyFactorsCard
keyFactors={keyFactorsData.key_factors}
cardBg={cardBg}
/>
)}
</GridItem>
<GridItem colSpan={{ base: 2, lg: 1 }}>
{keyFactorsData?.development_timeline && (
<TimelineCard
developmentTimeline={keyFactorsData.development_timeline}
cardBg={cardBg}
/>
)}
</GridItem>
</Grid>
</VStack>
</CardBody>
</Card>
);
};

View File

@@ -0,0 +1,50 @@
/**
* 业务结构 Tab
*
* 包含:业务结构分析 + 业务板块详情
*/
import React from 'react';
import TabPanelContainer from '@components/TabPanelContainer';
import { BusinessStructureCard, BusinessSegmentsCard } from '../components';
import type { ComprehensiveData } from '../types';
export interface BusinessTabProps {
comprehensiveData?: ComprehensiveData;
cardBg?: string;
expandedSegments: Record<number, boolean>;
onToggleSegment: (index: number) => void;
}
const BusinessTab: React.FC<BusinessTabProps> = ({
comprehensiveData,
cardBg,
expandedSegments,
onToggleSegment,
}) => {
return (
<TabPanelContainer showDisclaimer>
{/* 业务结构分析 */}
{comprehensiveData?.business_structure &&
comprehensiveData.business_structure.length > 0 && (
<BusinessStructureCard
businessStructure={comprehensiveData.business_structure}
cardBg={cardBg}
/>
)}
{/* 业务板块详情 */}
{comprehensiveData?.business_segments &&
comprehensiveData.business_segments.length > 0 && (
<BusinessSegmentsCard
businessSegments={comprehensiveData.business_segments}
expandedSegments={expandedSegments}
onToggleSegment={onToggleSegment}
cardBg={cardBg}
/>
)}
</TabPanelContainer>
);
};
export default BusinessTab;

View File

@@ -0,0 +1,47 @@
/**
* 发展历程 Tab
*
* 包含:关键因素 + 发展时间线Grid 布局)
*/
import React from 'react';
import { Grid, GridItem } from '@chakra-ui/react';
import TabPanelContainer from '@components/TabPanelContainer';
import { KeyFactorsCard, TimelineCard } from '../components';
import type { KeyFactorsData } from '../types';
export interface DevelopmentTabProps {
keyFactorsData?: KeyFactorsData;
cardBg?: string;
}
const DevelopmentTab: React.FC<DevelopmentTabProps> = ({
keyFactorsData,
cardBg,
}) => {
return (
<TabPanelContainer showDisclaimer>
<Grid templateColumns="repeat(2, 1fr)" gap={6}>
<GridItem colSpan={{ base: 2, lg: 1 }}>
{keyFactorsData?.key_factors && (
<KeyFactorsCard
keyFactors={keyFactorsData.key_factors}
cardBg={cardBg}
/>
)}
</GridItem>
<GridItem colSpan={{ base: 2, lg: 1 }}>
{keyFactorsData?.development_timeline && (
<TimelineCard
developmentTimeline={keyFactorsData.development_timeline}
cardBg={cardBg}
/>
)}
</GridItem>
</Grid>
</TabPanelContainer>
);
};
export default DevelopmentTab;

View File

@@ -0,0 +1,51 @@
/**
* 战略分析 Tab
*
* 包含:核心定位 + 战略分析 + 竞争地位分析
*/
import React from 'react';
import TabPanelContainer from '@components/TabPanelContainer';
import {
CorePositioningCard,
StrategyAnalysisCard,
CompetitiveAnalysisCard,
} from '../components';
import type { ComprehensiveData } from '../types';
export interface StrategyTabProps {
comprehensiveData?: ComprehensiveData;
cardBg?: string;
}
const StrategyTab: React.FC<StrategyTabProps> = ({
comprehensiveData,
cardBg,
}) => {
return (
<TabPanelContainer showDisclaimer>
{/* 核心定位卡片 */}
{comprehensiveData?.qualitative_analysis && (
<CorePositioningCard
qualitativeAnalysis={comprehensiveData.qualitative_analysis}
cardBg={cardBg}
/>
)}
{/* 战略分析 */}
{comprehensiveData?.qualitative_analysis?.strategy && (
<StrategyAnalysisCard
strategy={comprehensiveData.qualitative_analysis.strategy}
cardBg={cardBg}
/>
)}
{/* 竞争地位分析 */}
{comprehensiveData?.competitive_position && (
<CompetitiveAnalysisCard comprehensiveData={comprehensiveData} />
)}
</TabPanelContainer>
);
};
export default StrategyTab;

View File

@@ -0,0 +1,30 @@
/**
* 产业链 Tab
*
* 包含:产业链分析(层级视图 + Sankey 流向图)
*/
import React from 'react';
import TabPanelContainer from '@components/TabPanelContainer';
import { ValueChainCard } from '../components';
import type { ValueChainData } from '../types';
export interface ValueChainTabProps {
valueChainData?: ValueChainData;
cardBg?: string;
}
const ValueChainTab: React.FC<ValueChainTabProps> = ({
valueChainData,
cardBg,
}) => {
return (
<TabPanelContainer showDisclaimer>
{valueChainData && (
<ValueChainCard valueChainData={valueChainData} cardBg={cardBg} />
)}
</TabPanelContainer>
);
};
export default ValueChainTab;

View File

@@ -0,0 +1,14 @@
/**
* DeepAnalysisTab - Tab 组件导出
*/
export { default as StrategyTab } from './StrategyTab';
export { default as BusinessTab } from './BusinessTab';
export { default as ValueChainTab } from './ValueChainTab';
export { default as DevelopmentTab } from './DevelopmentTab';
// 导出类型
export type { StrategyTabProps } from './StrategyTab';
export type { BusinessTabProps } from './BusinessTab';
export type { ValueChainTabProps } from './ValueChainTab';
export type { DevelopmentTabProps } from './DevelopmentTab';