新增组件: - 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>
136 lines
3.7 KiB
TypeScript
136 lines
3.7 KiB
TypeScript
/**
|
||
* TabContainer 通用 Tab 容器组件
|
||
*
|
||
* 功能:
|
||
* - 管理 Tab 切换状态(支持受控/非受控模式)
|
||
* - 动态渲染 Tab 导航和内容
|
||
* - 支持多种主题预设(黑金、默认、深色、浅色)
|
||
* - 支持自定义主题颜色
|
||
* - 支持懒加载
|
||
*
|
||
* @example
|
||
* // 基础用法(传入 components)
|
||
* <TabContainer
|
||
* tabs={[
|
||
* { key: 'tab1', name: 'Tab 1', icon: FaHome, component: Tab1Content },
|
||
* { key: 'tab2', name: 'Tab 2', icon: FaUser, component: Tab2Content },
|
||
* ]}
|
||
* componentProps={{ userId: '123' }}
|
||
* onTabChange={(index, key) => console.log('切换到', key)}
|
||
* />
|
||
*
|
||
* @example
|
||
* // 自定义渲染用法(使用 children)
|
||
* <TabContainer tabs={tabs} themePreset="dark">
|
||
* <TabPanel>自定义内容 1</TabPanel>
|
||
* <TabPanel>自定义内容 2</TabPanel>
|
||
* </TabContainer>
|
||
*/
|
||
|
||
import React, { useState, useCallback, useMemo } from 'react';
|
||
import {
|
||
Card,
|
||
CardBody,
|
||
Tabs,
|
||
TabPanels,
|
||
TabPanel,
|
||
} from '@chakra-ui/react';
|
||
|
||
import TabNavigation from './TabNavigation';
|
||
import { THEME_PRESETS, DEFAULT_CONFIG } from './constants';
|
||
import type { TabContainerProps, ThemeColors } from './types';
|
||
|
||
// 导出类型和常量
|
||
export type { TabConfig, ThemeColors, ThemePreset, TabContainerProps } from './types';
|
||
export { THEME_PRESETS } from './constants';
|
||
|
||
const TabContainer: React.FC<TabContainerProps> = ({
|
||
tabs,
|
||
componentProps = {},
|
||
onTabChange,
|
||
defaultIndex = 0,
|
||
index: controlledIndex,
|
||
themePreset = DEFAULT_CONFIG.themePreset,
|
||
themeColors: customThemeColors,
|
||
isLazy = DEFAULT_CONFIG.isLazy,
|
||
size = DEFAULT_CONFIG.size,
|
||
showDivider = DEFAULT_CONFIG.showDivider,
|
||
borderRadius = DEFAULT_CONFIG.borderRadius,
|
||
shadow = DEFAULT_CONFIG.shadow,
|
||
panelPadding = DEFAULT_CONFIG.panelPadding,
|
||
children,
|
||
}) => {
|
||
// 内部状态(非受控模式)
|
||
const [internalIndex, setInternalIndex] = useState(defaultIndex);
|
||
|
||
// 当前索引(支持受控/非受控)
|
||
const currentIndex = controlledIndex ?? internalIndex;
|
||
|
||
// 合并主题颜色(自定义颜色优先)
|
||
const themeColors: Required<ThemeColors> = useMemo(() => ({
|
||
...THEME_PRESETS[themePreset],
|
||
...customThemeColors,
|
||
}), [themePreset, customThemeColors]);
|
||
|
||
/**
|
||
* 处理 Tab 切换
|
||
*/
|
||
const handleTabChange = useCallback((newIndex: number) => {
|
||
const tabKey = tabs[newIndex]?.key || '';
|
||
|
||
// 触发回调
|
||
onTabChange?.(newIndex, tabKey, currentIndex);
|
||
|
||
// 非受控模式下更新内部状态
|
||
if (controlledIndex === undefined) {
|
||
setInternalIndex(newIndex);
|
||
}
|
||
}, [tabs, onTabChange, currentIndex, controlledIndex]);
|
||
|
||
/**
|
||
* 渲染 Tab 内容
|
||
*/
|
||
const renderTabPanels = () => {
|
||
// 如果传入了 children,直接渲染 children
|
||
if (children) {
|
||
return children;
|
||
}
|
||
|
||
// 否则根据 tabs 配置渲染
|
||
return tabs.map((tab) => {
|
||
const Component = tab.component;
|
||
return (
|
||
<TabPanel key={tab.key} px={panelPadding} py={panelPadding}>
|
||
{Component ? <Component {...componentProps} /> : null}
|
||
</TabPanel>
|
||
);
|
||
});
|
||
};
|
||
|
||
return (
|
||
<Card shadow={shadow} bg={themeColors.bg} borderRadius={borderRadius}>
|
||
<CardBody p={0}>
|
||
<Tabs
|
||
isLazy={isLazy}
|
||
variant="unstyled"
|
||
size={size}
|
||
index={currentIndex}
|
||
onChange={handleTabChange}
|
||
>
|
||
{/* Tab 导航 */}
|
||
<TabNavigation
|
||
tabs={tabs}
|
||
themeColors={themeColors}
|
||
borderRadius={borderRadius}
|
||
/>
|
||
|
||
{/* Tab 内容面板 */}
|
||
<TabPanels>{renderTabPanels()}</TabPanels>
|
||
</Tabs>
|
||
</CardBody>
|
||
</Card>
|
||
);
|
||
};
|
||
|
||
export default TabContainer;
|