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;