// src/views/Company/components/CompanyOverview/BasicInfoTab.js // 基本信息 Tab - 股权结构、管理团队、公司公告、分支机构、工商信息 // 懒加载优化:使用 isLazy + 独立 Hooks,点击 Tab 时才加载对应数据 import React from "react"; import { Box, VStack, HStack, Text, Heading, Badge, Icon, Card, CardBody, CardHeader, SimpleGrid, Avatar, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Tag, Tooltip, Divider, Center, Code, Tabs, TabList, TabPanels, Tab, TabPanel, Stat, StatLabel, StatNumber, StatHelpText, IconButton, Button, Modal, ModalOverlay, ModalContent, ModalHeader, ModalCloseButton, ModalBody, ModalFooter, useDisclosure, Spinner, } from "@chakra-ui/react"; // 懒加载 Hooks import { useShareholderData } from "./hooks/useShareholderData"; import { useManagementData } from "./hooks/useManagementData"; import { useAnnouncementsData } from "./hooks/useAnnouncementsData"; import { useBranchesData } from "./hooks/useBranchesData"; import { useDisclosureData } from "./hooks/useDisclosureData"; import { ExternalLinkIcon } from "@chakra-ui/icons"; import { FaShareAlt, FaUserTie, FaBullhorn, FaSitemap, FaInfoCircle, FaCrown, FaChartPie, FaUsers, FaChartLine, FaArrowUp, FaArrowDown, FaChartBar, FaBuilding, FaGlobe, FaShieldAlt, FaBriefcase, FaCircle, FaEye, FaVenusMars, FaGraduationCap, FaPassport, FaCalendarAlt, } from "react-icons/fa"; // 格式化工具函数 const formatUtils = { formatPercentage: (value) => { if (value === null || value === undefined) return "-"; return `${(value * 100).toFixed(2)}%`; }, formatNumber: (value) => { if (value === null || value === undefined) return "-"; if (value >= 100000000) { return `${(value / 100000000).toFixed(2)}亿`; } else if (value >= 10000) { return `${(value / 10000).toFixed(2)}万`; } return value.toLocaleString(); }, formatShares: (value) => { if (value === null || value === undefined) return "-"; if (value >= 100000000) { return `${(value / 100000000).toFixed(2)}亿股`; } else if (value >= 10000) { return `${(value / 10000).toFixed(2)}万股`; } return `${value.toLocaleString()}股`; }, formatDate: (dateStr) => { if (!dateStr) return "-"; return dateStr.split("T")[0]; }, }; // 股东类型标签组件 const ShareholderTypeBadge = ({ type }) => { const typeConfig = { 基金: { color: "blue", icon: FaChartBar }, 个人: { color: "green", icon: FaUserTie }, 法人: { color: "purple", icon: FaBuilding }, QFII: { color: "orange", icon: FaGlobe }, 社保: { color: "red", icon: FaShieldAlt }, 保险: { color: "teal", icon: FaShieldAlt }, 信托: { color: "cyan", icon: FaBriefcase }, 券商: { color: "pink", icon: FaChartLine }, }; const config = Object.entries(typeConfig).find(([key]) => type?.includes(key) )?.[1] || { color: "gray", icon: FaCircle }; return ( {type} ); }; // ============================================ // 懒加载 TabPanel 子组件 // 每个子组件独立调用 Hook,配合 isLazy 实现真正的懒加载 // ============================================ /** * 股权结构 Tab Panel - 懒加载子组件 */ const ShareholderTabPanel = ({ stockCode }) => { const { actualControl, concentration, topShareholders, topCirculationShareholders, loading, } = useShareholderData(stockCode); // 计算股权集中度变化 const getConcentrationTrend = () => { const grouped = {}; concentration.forEach((item) => { if (!grouped[item.end_date]) { grouped[item.end_date] = {}; } grouped[item.end_date][item.stat_item] = item; }); return Object.entries(grouped) .sort((a, b) => b[0].localeCompare(a[0])) .slice(0, 5); }; if (loading) { return (
加载股权结构数据...
); } return ( {actualControl.length > 0 && ( 实际控制人 {actualControl[0].actual_controller_name} {actualControl[0].control_type} 截至 {formatUtils.formatDate(actualControl[0].end_date)} 控制比例 {formatUtils.formatPercentage(actualControl[0].holding_ratio)} {formatUtils.formatShares(actualControl[0].holding_shares)} )} {concentration.length > 0 && ( 股权集中度 {getConcentrationTrend() .slice(0, 1) .map(([date, items]) => ( {formatUtils.formatDate(date)} {Object.entries(items).map(([key, item]) => ( {item.stat_item} {formatUtils.formatPercentage(item.holding_ratio)} {item.ratio_change && ( 0 ? "red" : "green" } > 0 ? FaArrowUp : FaArrowDown } mr={1} boxSize={3} /> {Math.abs(item.ratio_change).toFixed(2)}% )} ))} ))} )} {topShareholders.length > 0 && ( 十大股东 {formatUtils.formatDate(topShareholders[0].end_date)} {topShareholders.slice(0, 10).map((shareholder, idx) => ( ))}
排名 股东名称 股东类型 持股数量 持股比例 股份性质
{shareholder.shareholder_rank} {shareholder.shareholder_name} {formatUtils.formatShares(shareholder.holding_shares)} {formatUtils.formatPercentage( shareholder.total_share_ratio )} {shareholder.share_nature || "流通股"}
)} {topCirculationShareholders.length > 0 && ( 十大流通股东 {formatUtils.formatDate(topCirculationShareholders[0].end_date)} {topCirculationShareholders.slice(0, 10).map((shareholder, idx) => ( ))}
排名 股东名称 股东类型 持股数量 流通股比例
{shareholder.shareholder_rank} {shareholder.shareholder_name} {formatUtils.formatShares(shareholder.holding_shares)} {formatUtils.formatPercentage( shareholder.circulation_share_ratio )}
)}
); }; /** * 管理团队 Tab Panel - 懒加载子组件 */ const ManagementTabPanel = ({ stockCode }) => { const { management, loading } = useManagementData(stockCode); // 管理层职位分类 const getManagementByCategory = () => { const categories = { 高管: [], 董事: [], 监事: [], 其他: [], }; management.forEach((person) => { 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; }; if (loading) { return (
加载管理团队数据...
); } return ( {Object.entries(getManagementByCategory()).map( ([category, people]) => people.length > 0 && ( {category} {people.length}人 {people.map((person, idx) => ( {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} )} 任职日期:{formatUtils.formatDate(person.start_date)} ))} ) )} ); }; /** * 公司公告 Tab Panel - 懒加载子组件 */ const AnnouncementsTabPanel = ({ stockCode }) => { const { announcements, loading: announcementsLoading } = useAnnouncementsData(stockCode); const { disclosureSchedule, loading: disclosureLoading } = useDisclosureData(stockCode); const { isOpen, onOpen, onClose } = useDisclosure(); const [selectedAnnouncement, setSelectedAnnouncement] = React.useState(null); const handleAnnouncementClick = (announcement) => { setSelectedAnnouncement(announcement); onOpen(); }; const loading = announcementsLoading || disclosureLoading; if (loading) { return (
加载公告数据...
); } return ( <> {disclosureSchedule.length > 0 && ( 财报披露日程 {disclosureSchedule.slice(0, 4).map((schedule, idx) => ( {schedule.report_name} {schedule.is_disclosed ? "已披露" : "预计"} {formatUtils.formatDate( schedule.is_disclosed ? schedule.actual_date : schedule.latest_scheduled_date )} ))} )} 最新公告 {announcements.map((announcement, idx) => ( handleAnnouncementClick(announcement)} _hover={{ bg: "gray.50" }} > {announcement.info_type || "公告"} {formatUtils.formatDate(announcement.announce_date)} {announcement.title} {announcement.format && ( {announcement.format} )} } variant="ghost" onClick={(e) => { e.stopPropagation(); window.open(announcement.url, "_blank"); }} /> ))} {/* 公告详情模态框 */} {selectedAnnouncement?.title} {selectedAnnouncement?.info_type || "公告"} {formatUtils.formatDate(selectedAnnouncement?.announce_date)} 文件格式:{selectedAnnouncement?.format || "-"} 文件大小:{selectedAnnouncement?.file_size || "-"} KB ); }; /** * 分支机构 Tab Panel - 懒加载子组件 */ const BranchesTabPanel = ({ stockCode }) => { const { branches, loading } = useBranchesData(stockCode); if (loading) { return (
加载分支机构数据...
); } if (branches.length === 0) { return (
暂无分支机构信息
); } return ( {branches.map((branch, idx) => ( {branch.branch_name} {branch.business_status} 注册资本 {branch.register_capital || "-"} 法人代表 {branch.legal_person || "-"} 成立日期 {formatUtils.formatDate(branch.register_date)} 关联企业 {branch.related_company_count || 0} 家 ))} ); }; /** * 工商信息 Tab Panel - 使用父组件传入的 basicInfo */ const BusinessInfoTabPanel = ({ basicInfo }) => { if (!basicInfo) { return (
暂无工商信息
); } return ( 工商信息 统一信用代码 {basicInfo.credit_code} 公司规模 {basicInfo.company_size} 注册地址 {basicInfo.reg_address} 办公地址 {basicInfo.office_address} 服务机构 会计师事务所 {basicInfo.accounting_firm} 律师事务所 {basicInfo.law_firm} 主营业务 {basicInfo.main_business} 经营范围 {basicInfo.business_scope} ); }; // ============================================ // 主组件 // ============================================ /** * 基本信息 Tab 组件(懒加载版本) * * Props: * - stockCode: 股票代码(用于懒加载数据) * - basicInfo: 公司基本信息(从父组件传入,用于工商信息 Tab) * - cardBg: 卡片背景色 * * 懒加载策略: * - 使用 Chakra UI Tabs 的 isLazy 属性 * - 每个 TabPanel 使用独立子组件,在首次激活时才渲染并加载数据 */ const BasicInfoTab = ({ stockCode, basicInfo, cardBg }) => { return ( 股权结构 管理团队 公司公告 分支机构 工商信息 {/* 股权结构 - 懒加载 */} {/* 管理团队 - 懒加载 */} {/* 公司公告 - 懒加载 */} {/* 分支机构 - 懒加载 */} {/* 工商信息 - 使用父组件传入的 basicInfo */} ); }; export default BasicInfoTab;