- TabPanelContainer: Loading 颜色改为金色 #D4AF37,与黑金主题一致 - SubTabContainer: 添加 memo 和 displayName - 子 Tab 组件: StrategyTab/BusinessTab/ValueChainTab/DevelopmentTab 添加 memo 和 displayName - TabContainer: 移除未使用的 showDivider 参数 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
135 lines
3.6 KiB
TypeScript
135 lines
3.6 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,
|
||
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;
|