From a47e0feed862907b839c85851bd12b702c769dea Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Thu, 11 Dec 2025 16:59:17 +0800 Subject: [PATCH] =?UTF-8?q?refactor(TabContainer):=20=E6=8A=BD=E5=8F=96?= =?UTF-8?q?=E9=80=9A=E7=94=A8=20Tab=20=E5=AE=B9=E5=99=A8=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 src/components/TabContainer/ 通用组件 - 支持受控/非受控模式 - 支持多种主题预设(blackGold、default、dark、light) - 支持自定义主题颜色和样式配置 - 使用 TypeScript 实现,类型完整 - 重构 CompanyTabs 使用通用 TabContainer - 删除 CompanyTabs/TabNavigation.js(逻辑迁移到通用组件) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/TabContainer/TabNavigation.tsx | 49 ++++++ src/components/TabContainer/constants.ts | 56 +++++++ src/components/TabContainer/index.tsx | 140 ++++++++++++++++++ src/components/TabContainer/types.ts | 87 +++++++++++ .../components/CompanyTabs/TabNavigation.js | 55 ------- .../Company/components/CompanyTabs/index.js | 84 ++++------- 6 files changed, 362 insertions(+), 109 deletions(-) create mode 100644 src/components/TabContainer/TabNavigation.tsx create mode 100644 src/components/TabContainer/constants.ts create mode 100644 src/components/TabContainer/index.tsx create mode 100644 src/components/TabContainer/types.ts delete mode 100644 src/views/Company/components/CompanyTabs/TabNavigation.js diff --git a/src/components/TabContainer/TabNavigation.tsx b/src/components/TabContainer/TabNavigation.tsx new file mode 100644 index 00000000..ac6994b2 --- /dev/null +++ b/src/components/TabContainer/TabNavigation.tsx @@ -0,0 +1,49 @@ +/** + * TabNavigation 通用导航组件 + * + * 渲染 Tab 按钮列表,支持图标 + 文字 + */ + +import React from 'react'; +import { TabList, Tab, HStack, Icon, Text } from '@chakra-ui/react'; +import type { TabNavigationProps } from './types'; + +const TabNavigation: React.FC = ({ + tabs, + themeColors, + borderRadius = 'lg', +}) => { + return ( + + {tabs.map((tab, index) => ( + + + {tab.icon && } + {tab.name} + + + ))} + + ); +}; + +export default TabNavigation; diff --git a/src/components/TabContainer/constants.ts b/src/components/TabContainer/constants.ts new file mode 100644 index 00000000..40c2e6ec --- /dev/null +++ b/src/components/TabContainer/constants.ts @@ -0,0 +1,56 @@ +/** + * TabContainer 常量和主题预设 + */ + +import type { ThemeColors, ThemePreset } from './types'; + +/** + * 主题预设配置 + */ +export const THEME_PRESETS: Record> = { + // 黑金主题(原 Company 模块风格) + blackGold: { + bg: '#1A202C', + selectedBg: '#C9A961', + selectedText: '#FFFFFF', + unselectedText: '#D4AF37', + dividerColor: 'gray.600', + }, + // 默认主题(Chakra 风格) + default: { + bg: 'white', + selectedBg: 'blue.500', + selectedText: 'white', + unselectedText: 'gray.600', + dividerColor: 'gray.200', + }, + // 深色主题 + dark: { + bg: 'gray.800', + selectedBg: 'blue.400', + selectedText: 'white', + unselectedText: 'gray.300', + dividerColor: 'gray.600', + }, + // 浅色主题 + light: { + bg: 'gray.50', + selectedBg: 'blue.500', + selectedText: 'white', + unselectedText: 'gray.700', + dividerColor: 'gray.300', + }, +}; + +/** + * 默认配置 + */ +export const DEFAULT_CONFIG = { + themePreset: 'blackGold' as ThemePreset, + isLazy: true, + size: 'lg' as const, + showDivider: true, + borderRadius: 'lg', + shadow: 'lg', + panelPadding: 0, +}; diff --git a/src/components/TabContainer/index.tsx b/src/components/TabContainer/index.tsx new file mode 100644 index 00000000..c40e3269 --- /dev/null +++ b/src/components/TabContainer/index.tsx @@ -0,0 +1,140 @@ +/** + * TabContainer 通用 Tab 容器组件 + * + * 功能: + * - 管理 Tab 切换状态(支持受控/非受控模式) + * - 动态渲染 Tab 导航和内容 + * - 支持多种主题预设(黑金、默认、深色、浅色) + * - 支持自定义主题颜色 + * - 支持懒加载 + * + * @example + * // 基础用法(传入 components) + * console.log('切换到', key)} + * /> + * + * @example + * // 自定义渲染用法(使用 children) + * + * 自定义内容 1 + * 自定义内容 2 + * + */ + +import React, { useState, useCallback, useMemo } from 'react'; +import { + Card, + CardBody, + Tabs, + TabPanels, + TabPanel, + Divider, +} 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 = ({ + 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 = 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 ( + + {Component ? : null} + + ); + }); + }; + + return ( + + + + {/* Tab 导航 */} + + + {/* 分割线 */} + {showDivider && } + + {/* Tab 内容面板 */} + {renderTabPanels()} + + + + ); +}; + +export default TabContainer; diff --git a/src/components/TabContainer/types.ts b/src/components/TabContainer/types.ts new file mode 100644 index 00000000..dcf959d3 --- /dev/null +++ b/src/components/TabContainer/types.ts @@ -0,0 +1,87 @@ +/** + * TabContainer 通用 Tab 容器组件类型定义 + */ + +import type { ComponentType, ReactNode } from 'react'; +import type { IconType } from 'react-icons'; + +/** + * Tab 配置项 + */ +export interface TabConfig { + /** Tab 唯一标识 */ + key: string; + /** Tab 显示名称 */ + name: string; + /** Tab 图标(可选) */ + icon?: IconType | ComponentType; + /** Tab 内容组件(可选,如果不传则使用 children 渲染) */ + component?: ComponentType; +} + +/** + * 主题颜色配置 + */ +export interface ThemeColors { + /** 容器背景色 */ + bg?: string; + /** 选中 Tab 背景色 */ + selectedBg?: string; + /** 选中 Tab 文字颜色 */ + selectedText?: string; + /** 未选中 Tab 文字颜色 */ + unselectedText?: string; + /** 分割线颜色 */ + dividerColor?: string; +} + +/** + * 预设主题类型 + */ +export type ThemePreset = 'blackGold' | 'default' | 'dark' | 'light'; + +/** + * TabContainer 组件 Props + */ +export interface TabContainerProps { + /** Tab 配置数组 */ + tabs: TabConfig[]; + /** 传递给 Tab 内容组件的通用 props */ + componentProps?: Record; + /** Tab 变更回调 */ + onTabChange?: (index: number, tabKey: string, prevIndex: number) => void; + /** 默认选中的 Tab 索引 */ + defaultIndex?: number; + /** 受控模式下的当前索引 */ + index?: number; + /** 主题预设 */ + themePreset?: ThemePreset; + /** 自定义主题颜色(优先级高于预设) */ + themeColors?: ThemeColors; + /** 是否启用懒加载 */ + isLazy?: boolean; + /** Tab 尺寸 */ + size?: 'sm' | 'md' | 'lg'; + /** 是否显示分割线 */ + showDivider?: boolean; + /** 容器圆角 */ + borderRadius?: string; + /** 容器阴影 */ + shadow?: string; + /** 自定义 Tab 面板内边距 */ + panelPadding?: number | string; + /** 子元素(用于自定义渲染 Tab 内容) */ + children?: ReactNode; +} + +/** + * TabNavigation 组件 Props + */ +export interface TabNavigationProps { + /** Tab 配置数组 */ + tabs: TabConfig[]; + /** 主题颜色 */ + themeColors: Required; + /** 容器圆角 */ + borderRadius?: string; +} diff --git a/src/views/Company/components/CompanyTabs/TabNavigation.js b/src/views/Company/components/CompanyTabs/TabNavigation.js deleted file mode 100644 index ceadcccd..00000000 --- a/src/views/Company/components/CompanyTabs/TabNavigation.js +++ /dev/null @@ -1,55 +0,0 @@ -// src/views/Company/components/CompanyTabs/TabNavigation.js -// Tab 导航组件 - 动态渲染 Tab 按钮(黑金主题) - -import React from 'react'; -import { - TabList, - Tab, - HStack, - Icon, - Text, -} from '@chakra-ui/react'; - -import { COMPANY_TABS } from '../../constants'; - -// 黑金主题颜色配置 -const THEME_COLORS = { - bg: '#1A202C', // 背景纯黑 - selectedBg: '#C9A961', // 选中项金色背景 - selectedText: '#FFFFFF', // 选中项白色文字 - unselectedText: '#D4AF37', // 未选中项金色 -}; - -/** - * Tab 导航组件(黑金主题) - */ -const TabNavigation = () => { - return ( - - {COMPANY_TABS.map((tab, index) => ( - - - - {tab.name} - - - ))} - - ); -}; - -export default TabNavigation; diff --git a/src/views/Company/components/CompanyTabs/index.js b/src/views/Company/components/CompanyTabs/index.js index 03b105df..41e4b6f2 100644 --- a/src/views/Company/components/CompanyTabs/index.js +++ b/src/views/Company/components/CompanyTabs/index.js @@ -1,17 +1,8 @@ // src/views/Company/components/CompanyTabs/index.js -// Tab 容器组件 - 管理 Tab 切换和内容渲染 +// Tab 容器组件 - 使用通用 TabContainer 组件 -import React, { useState } from 'react'; -import { - Card, - CardBody, - Tabs, - TabPanels, - TabPanel, - Divider, -} from '@chakra-ui/react'; - -import TabNavigation from './TabNavigation'; +import React from 'react'; +import TabContainer from '@components/TabContainer'; import { COMPANY_TABS, getTabNameByIndex } from '../../constants'; // 子组件导入(Tab 内容组件) @@ -24,7 +15,6 @@ import DynamicTracking from '../DynamicTracking'; /** * Tab 组件映射 - * key 与 COMPANY_TABS 中的 key 对应 */ const TAB_COMPONENTS = { overview: CompanyOverview, @@ -36,11 +26,25 @@ const TAB_COMPONENTS = { }; /** - * Tab 容器组件 + * 构建 TabContainer 所需的 tabs 配置 + * 合并 COMPANY_TABS 和对应的组件 + */ +const buildTabsConfig = () => { + return COMPANY_TABS.map((tab) => ({ + ...tab, + component: TAB_COMPONENTS[tab.key], + })); +}; + +// 预构建 tabs 配置(避免每次渲染重新计算) +const TABS_CONFIG = buildTabsConfig(); + +/** + * 公司详情 Tab 容器组件 * * 功能: - * - 管理 Tab 切换状态 - * - 动态渲染 Tab 导航和内容 + * - 使用通用 TabContainer 组件 + * - 保持黑金主题风格 * - 触发 Tab 变更追踪 * * @param {Object} props @@ -48,51 +52,23 @@ const TAB_COMPONENTS = { * @param {Function} props.onTabChange - Tab 变更回调 (index, tabName, prevIndex) => void */ const CompanyTabs = ({ stockCode, onTabChange }) => { - const [currentIndex, setCurrentIndex] = useState(0); - /** * 处理 Tab 切换 + * 转换 tabKey 为 tabName 以保持原有回调格式 */ - const handleTabChange = (index) => { + const handleTabChange = (index, tabKey, prevIndex) => { const tabName = getTabNameByIndex(index); - - // 触发追踪回调 - onTabChange?.(index, tabName, currentIndex); - - // 更新状态 - setCurrentIndex(index); + onTabChange?.(index, tabName, prevIndex); }; return ( - - - - {/* Tab 导航(黑金主题) */} - - - - - {/* Tab 内容面板 */} - - {COMPANY_TABS.map((tab) => { - const Component = TAB_COMPONENTS[tab.key]; - return ( - - - - ); - })} - - - - + ); };