diff --git a/src/views/Company/STRUCTURE.md b/src/views/Company/STRUCTURE.md index c429b6ce..ea13c2d8 100644 --- a/src/views/Company/STRUCTURE.md +++ b/src/views/Company/STRUCTURE.md @@ -30,7 +30,12 @@ src/views/Company/ │ │ │ ├── index.ts # 组件统一导出 │ │ │ ├── LoadingState.tsx # 加载状态组件(黑金主题 Spinner) │ │ │ ├── ShareholderPanel.tsx # 股权结构面板(实控人、十大股东、股权集中度) -│ │ │ ├── ManagementPanel.tsx # 管理团队面板(高管列表表格) +│ │ │ ├── management/ # 管理团队模块(拆分重构) +│ │ │ │ ├── index.ts # 模块导出 +│ │ │ │ ├── types.ts # 类型定义(ManagementPerson 等) +│ │ │ │ ├── ManagementPanel.tsx # 主组件(useMemo 优化) +│ │ │ │ ├── CategorySection.tsx # 分类区块(memo 优化) +│ │ │ │ └── ManagementCard.tsx # 人员卡片(memo 优化) │ │ │ ├── AnnouncementsPanel.tsx # 公告信息面板(公告列表 + 披露日程) │ │ │ ├── BranchesPanel.tsx # 分支机构面板(分支列表表格) │ │ │ └── BusinessInfoPanel.tsx # 工商信息面板(注册资本、成立日期等) @@ -468,4 +473,37 @@ MarketDataView/ - **TypeScript 类型安全**:所有数据结构有完整类型定义 - **服务层分离**:API 调用统一在 `marketService.ts` 中管理 - **图表配置抽离**:复杂的 ECharts 配置集中在 `chartOptions.ts` -- **组件复用**:通用组件(ThemedCard、MarkdownRenderer)可在其他模块使用 \ No newline at end of file +- **组件复用**:通用组件(ThemedCard、MarkdownRenderer)可在其他模块使用 + +### 2025-12-10 ManagementPanel 拆分重构 + +**改动概述**: +- `ManagementPanel.tsx` 从 **180 行** 拆分为 **5 个 TypeScript 文件** +- 创建 `management/` 子目录,模块化管理 +- 添加性能优化(`useMemo`、`React.memo`) + +**拆分后文件结构**: +``` +components/management/ +├── index.ts # 模块导出 +├── types.ts # 类型定义(~35 行) +├── ManagementPanel.tsx # 主组件(~105 行,useMemo 优化) +├── CategorySection.tsx # 分类区块组件(~65 行,memo) +└── ManagementCard.tsx # 人员卡片组件(~100 行,memo) +``` + +**类型定义**(`types.ts`): +- `ManagementPerson` - 管理人员信息 +- `ManagementCategory` - 分类类型(高管/董事/监事/其他) +- `CategorizedManagement` - 分类后的数据结构 +- `CategoryConfig` - 分类配置(图标、颜色) + +**性能优化**: +- `useMemo` - 缓存 `categorizeManagement()` 分类计算结果 +- `React.memo` - `ManagementCard` 和 `CategorySection` 使用 memo 包装 +- 常量提取 - `CATEGORY_CONFIG` 和 `CATEGORY_ORDER` 提取到组件外部 + +**设计原则**: +- **职责分离**:卡片渲染、分类区块、数据处理各自独立 +- **类型安全**:消除 `any` 类型,完整的 TypeScript 类型定义 +- **可复用性**:`ManagementCard` 可独立使用 \ No newline at end of file diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/ManagementPanel.tsx b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/ManagementPanel.tsx deleted file mode 100644 index 84293aa0..00000000 --- a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/ManagementPanel.tsx +++ /dev/null @@ -1,179 +0,0 @@ -// src/views/Company/components/CompanyOverview/BasicInfoTab/components/ManagementPanel.tsx -// 管理团队 Tab Panel - -import React from "react"; -import { - Box, - VStack, - HStack, - Text, - Heading, - Badge, - Icon, - Card, - CardBody, - SimpleGrid, - Avatar, - Tag, -} from "@chakra-ui/react"; -import { - FaUserTie, - FaCrown, - FaEye, - FaUsers, - FaVenusMars, - FaGraduationCap, - FaPassport, -} from "react-icons/fa"; - -import { useManagementData } from "../../hooks/useManagementData"; -import { THEME } from "../config"; -import { formatDate } from "../utils"; -import LoadingState from "./LoadingState"; - -interface ManagementPanelProps { - stockCode: string; -} - -const ManagementPanel: React.FC = ({ stockCode }) => { - const { management, loading } = useManagementData(stockCode); - - // 管理层职位分类 - const getManagementByCategory = () => { - const categories: Record = { - 高管: [], - 董事: [], - 监事: [], - 其他: [], - }; - - management.forEach((person: any) => { - if ( - person.position_category === "高管" || - person.position_name?.includes("总") - ) { - categories["高管"].push(person); - } else if ( - person.position_category === "董事" || - person.position_name?.includes("董事") - ) { - categories["董事"].push(person); - } else if ( - person.position_category === "监事" || - person.position_name?.includes("监事") - ) { - categories["监事"].push(person); - } else { - categories["其他"].push(person); - } - }); - - return categories; - }; - - const getCategoryIcon = (category: string) => { - switch (category) { - case "高管": - return FaUserTie; - case "董事": - return FaCrown; - case "监事": - return FaEye; - default: - return FaUsers; - } - }; - - const getCategoryColor = (category: string) => { - switch (category) { - case "高管": - return THEME.gold; - case "董事": - return THEME.goldLight; - case "监事": - return "green.400"; - default: - return THEME.textSecondary; - } - }; - - if (loading) { - return ; - } - - return ( - - {Object.entries(getManagementByCategory()).map( - ([category, people]) => - people.length > 0 && ( - - - - {category} - {people.length}人 - - - - {people.map((person: any, idx: number) => ( - - - - - - - {person.name} - {person.gender && ( - - )} - - - {person.position_name} - - - {person.education && ( - - - {person.education} - - )} - {person.birth_year && ( - - {new Date().getFullYear() - parseInt(person.birth_year)}岁 - - )} - {person.nationality && person.nationality !== "中国" && ( - - - {person.nationality} - - )} - - - 任职日期:{formatDate(person.start_date)} - - - - - - ))} - - - ) - )} - - ); -}; - -export default ManagementPanel; diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/index.ts b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/index.ts index f3cc4334..aae3d653 100644 --- a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/index.ts +++ b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/index.ts @@ -3,7 +3,7 @@ export { default as LoadingState } from "./LoadingState"; export { default as ShareholderPanel } from "./ShareholderPanel"; -export { default as ManagementPanel } from "./ManagementPanel"; +export { ManagementPanel } from "./management"; export { default as AnnouncementsPanel } from "./AnnouncementsPanel"; export { default as BranchesPanel } from "./BranchesPanel"; export { default as BusinessInfoPanel } from "./BusinessInfoPanel"; diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/CategorySection.tsx b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/CategorySection.tsx new file mode 100644 index 00000000..80b20ee9 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/CategorySection.tsx @@ -0,0 +1,63 @@ +// src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/CategorySection.tsx +// 管理层分类区块组件 + +import React, { memo } from "react"; +import { + Box, + HStack, + Heading, + Badge, + Icon, + SimpleGrid, +} from "@chakra-ui/react"; +import type { IconType } from "react-icons"; + +import { THEME } from "../../config"; +import ManagementCard from "./ManagementCard"; +import type { ManagementPerson, ManagementCategory } from "./types"; + +interface CategorySectionProps { + category: ManagementCategory; + people: ManagementPerson[]; + icon: IconType; + color: string; +} + +const CategorySection: React.FC = ({ + category, + people, + icon, + color, +}) => { + if (people.length === 0) { + return null; + } + + return ( + + {/* 分类标题 */} + + + + {category} + + + {people.length}人 + + + + {/* 人员卡片网格 */} + + {people.map((person, idx) => ( + + ))} + + + ); +}; + +export default memo(CategorySection); diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementCard.tsx b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementCard.tsx new file mode 100644 index 00000000..433a36a5 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementCard.tsx @@ -0,0 +1,100 @@ +// src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementCard.tsx +// 管理人员卡片组件 + +import React, { memo } from "react"; +import { + HStack, + VStack, + Text, + Icon, + Card, + CardBody, + Avatar, + Tag, +} from "@chakra-ui/react"; +import { + FaVenusMars, + FaGraduationCap, + FaPassport, +} from "react-icons/fa"; + +import { THEME } from "../../config"; +import { formatDate } from "../../utils"; +import type { ManagementPerson } from "./types"; + +interface ManagementCardProps { + person: ManagementPerson; + categoryColor: string; +} + +const ManagementCard: React.FC = ({ person, categoryColor }) => { + const currentYear = new Date().getFullYear(); + const age = person.birth_year ? currentYear - parseInt(person.birth_year, 10) : null; + + return ( + + + + + + {/* 姓名和性别 */} + + + {person.name} + + {person.gender && ( + + )} + + + {/* 职位 */} + + {person.position_name} + + + {/* 标签:学历、年龄、国籍 */} + + {person.education && ( + + + {person.education} + + )} + {age && ( + + {age}岁 + + )} + {person.nationality && person.nationality !== "中国" && ( + + + {person.nationality} + + )} + + + {/* 任职日期 */} + + 任职日期:{formatDate(person.start_date)} + + + + + + ); +}; + +export default memo(ManagementCard); diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementPanel.tsx b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementPanel.tsx new file mode 100644 index 00000000..23a72705 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementPanel.tsx @@ -0,0 +1,105 @@ +// src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/ManagementPanel.tsx +// 管理团队 Tab Panel(重构版) + +import React, { useMemo } from "react"; +import { VStack } from "@chakra-ui/react"; +import { + FaUserTie, + FaCrown, + FaEye, + FaUsers, +} from "react-icons/fa"; + +import { useManagementData } from "../../../hooks/useManagementData"; +import { THEME } from "../../config"; +import LoadingState from "../LoadingState"; +import CategorySection from "./CategorySection"; +import type { + ManagementPerson, + ManagementCategory, + CategorizedManagement, + CategoryConfig, +} from "./types"; + +interface ManagementPanelProps { + stockCode: string; +} + +/** + * 分类配置映射 + */ +const CATEGORY_CONFIG: Record = { + 高管: { icon: FaUserTie, color: THEME.gold }, + 董事: { icon: FaCrown, color: THEME.goldLight }, + 监事: { icon: FaEye, color: "green.400" }, + 其他: { icon: FaUsers, color: THEME.textSecondary }, +}; + +/** + * 分类顺序 + */ +const CATEGORY_ORDER: ManagementCategory[] = ["高管", "董事", "监事", "其他"]; + +/** + * 根据职位信息对管理人员进行分类 + */ +const categorizeManagement = (management: ManagementPerson[]): CategorizedManagement => { + const categories: CategorizedManagement = { + 高管: [], + 董事: [], + 监事: [], + 其他: [], + }; + + management.forEach((person) => { + const positionCategory = person.position_category; + const positionName = person.position_name || ""; + + if (positionCategory === "高管" || positionName.includes("总")) { + categories["高管"].push(person); + } else if (positionCategory === "董事" || positionName.includes("董事")) { + categories["董事"].push(person); + } else if (positionCategory === "监事" || positionName.includes("监事")) { + categories["监事"].push(person); + } else { + categories["其他"].push(person); + } + }); + + return categories; +}; + +const ManagementPanel: React.FC = ({ stockCode }) => { + const { management, loading } = useManagementData(stockCode); + + // 使用 useMemo 缓存分类计算结果 + const categorizedManagement = useMemo( + () => categorizeManagement(management as ManagementPerson[]), + [management] + ); + + if (loading) { + return ; + } + + return ( + + {CATEGORY_ORDER.map((category) => { + const config = CATEGORY_CONFIG[category]; + const people = categorizedManagement[category]; + + return ( + + ); + })} + + ); +}; + +export default ManagementPanel; diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/index.ts b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/index.ts new file mode 100644 index 00000000..f61b4ab4 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/index.ts @@ -0,0 +1,7 @@ +// src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/index.ts +// 管理团队组件导出 + +export { default as ManagementPanel } from "./ManagementPanel"; +export { default as ManagementCard } from "./ManagementCard"; +export { default as CategorySection } from "./CategorySection"; +export * from "./types"; diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/types.ts b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/types.ts new file mode 100644 index 00000000..81cac215 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/types.ts @@ -0,0 +1,36 @@ +// src/views/Company/components/CompanyOverview/BasicInfoTab/components/management/types.ts +// 管理团队相关类型定义 + +import type { IconType } from "react-icons"; + +/** + * 管理人员信息 + */ +export interface ManagementPerson { + name: string; + position_name?: string; + position_category?: string; + gender?: "男" | "女"; + education?: string; + birth_year?: string; + nationality?: string; + start_date?: string; +} + +/** + * 管理层分类 + */ +export type ManagementCategory = "高管" | "董事" | "监事" | "其他"; + +/** + * 分类后的管理层数据 + */ +export type CategorizedManagement = Record; + +/** + * 分类配置项 + */ +export interface CategoryConfig { + icon: IconType; + color: string; +}