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:
195
src/components/SubTabContainer/index.tsx
Normal file
195
src/components/SubTabContainer/index.tsx
Normal 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;
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
|
||||
100
src/components/TabPanelContainer/index.tsx
Normal file
100
src/components/TabPanelContainer/index.tsx
Normal 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;
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ConcentrationCard,
|
||||
ShareholdersTable,
|
||||
} from "../../components/shareholder";
|
||||
import TabPanelContainer from "./TabPanelContainer";
|
||||
import TabPanelContainer from "@components/TabPanelContainer";
|
||||
|
||||
interface ShareholderPanelProps {
|
||||
stockCode: string;
|
||||
|
||||
@@ -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;
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 通用组件
|
||||
* - 可配置显示哪些 Tab(enabledTabs)
|
||||
* - 黑金主题
|
||||
* - 懒加载(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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
Reference in New Issue
Block a user