Files
vf_react/src/views/Company/components/CompanyOverview/BasicInfoTab.js
zdl 5f6e4387e5 perf: CompanyOverview 内层 Tab 懒加载优化
- 将 useCompanyOverviewData(9个API)拆分为独立 Hooks:
  - useBasicInfo: 基本信息(首屏唯一加载)
  - useShareholderData: 股东信息(4个API)
  - useManagementData: 管理层信息
  - useAnnouncementsData: 公告数据
  - useBranchesData: 分支机构
  - useDisclosureData: 披露日程
- BasicInfoTab 使用子组件实现真正的懒加载:
  - ShareholderTabPanel、ManagementTabPanel 等
  - 配合 Chakra UI isLazy,切换 Tab 时才加载数据
- 首屏 API 请求从 9 个减少到 1 个

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-10 13:05:27 +08:00

995 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 (
<Badge colorScheme={config.color} size="sm">
<Icon as={config.icon} mr={1} boxSize={3} />
{type}
</Badge>
);
};
// ============================================
// 懒加载 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 (
<Center h="200px">
<VStack>
<Spinner size="lg" color="blue.500" />
<Text fontSize="sm" color="gray.500">
加载股权结构数据...
</Text>
</VStack>
</Center>
);
}
return (
<VStack spacing={6} align="stretch">
{actualControl.length > 0 && (
<Box>
<HStack mb={4}>
<Icon as={FaCrown} color="gold" boxSize={5} />
<Heading size="sm">实际控制人</Heading>
</HStack>
<Card variant="outline">
<CardBody>
<HStack justify="space-between">
<VStack align="start">
<Text fontWeight="bold" fontSize="lg">
{actualControl[0].actual_controller_name}
</Text>
<HStack>
<Badge colorScheme="purple">
{actualControl[0].control_type}
</Badge>
<Text fontSize="sm" color="gray.500">
截至 {formatUtils.formatDate(actualControl[0].end_date)}
</Text>
</HStack>
</VStack>
<Stat textAlign="right">
<StatLabel>控制比例</StatLabel>
<StatNumber color="purple.500">
{formatUtils.formatPercentage(actualControl[0].holding_ratio)}
</StatNumber>
<StatHelpText>
{formatUtils.formatShares(actualControl[0].holding_shares)}
</StatHelpText>
</Stat>
</HStack>
</CardBody>
</Card>
</Box>
)}
{concentration.length > 0 && (
<Box>
<HStack mb={4}>
<Icon as={FaChartPie} color="blue.500" boxSize={5} />
<Heading size="sm">股权集中度</Heading>
</HStack>
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
{getConcentrationTrend()
.slice(0, 1)
.map(([date, items]) => (
<Card key={date} variant="outline">
<CardHeader pb={2}>
<Text fontSize="sm" color="gray.500">
{formatUtils.formatDate(date)}
</Text>
</CardHeader>
<CardBody pt={2}>
<VStack spacing={3} align="stretch">
{Object.entries(items).map(([key, item]) => (
<HStack key={key} justify="space-between">
<Text fontSize="sm">{item.stat_item}</Text>
<HStack>
<Text fontWeight="bold" color="blue.500">
{formatUtils.formatPercentage(item.holding_ratio)}
</Text>
{item.ratio_change && (
<Badge
colorScheme={
item.ratio_change > 0 ? "red" : "green"
}
>
<Icon
as={
item.ratio_change > 0 ? FaArrowUp : FaArrowDown
}
mr={1}
boxSize={3}
/>
{Math.abs(item.ratio_change).toFixed(2)}%
</Badge>
)}
</HStack>
</HStack>
))}
</VStack>
</CardBody>
</Card>
))}
</SimpleGrid>
</Box>
)}
{topShareholders.length > 0 && (
<Box>
<HStack mb={4}>
<Icon as={FaUsers} color="green.500" boxSize={5} />
<Heading size="sm">十大股东</Heading>
<Badge>
{formatUtils.formatDate(topShareholders[0].end_date)}
</Badge>
</HStack>
<TableContainer>
<Table size="sm" variant="striped">
<Thead>
<Tr>
<Th>排名</Th>
<Th>股东名称</Th>
<Th>股东类型</Th>
<Th isNumeric>持股数量</Th>
<Th isNumeric>持股比例</Th>
<Th>股份性质</Th>
</Tr>
</Thead>
<Tbody>
{topShareholders.slice(0, 10).map((shareholder, idx) => (
<Tr key={idx}>
<Td>
<Badge colorScheme={idx < 3 ? "red" : "gray"}>
{shareholder.shareholder_rank}
</Badge>
</Td>
<Td>
<Tooltip label={shareholder.shareholder_name}>
<Text noOfLines={1} maxW="200px">
{shareholder.shareholder_name}
</Text>
</Tooltip>
</Td>
<Td>
<ShareholderTypeBadge type={shareholder.shareholder_type} />
</Td>
<Td isNumeric fontWeight="medium">
{formatUtils.formatShares(shareholder.holding_shares)}
</Td>
<Td isNumeric>
<Text color="blue.500" fontWeight="bold">
{formatUtils.formatPercentage(
shareholder.total_share_ratio
)}
</Text>
</Td>
<Td>
<Badge size="sm" variant="outline">
{shareholder.share_nature || "流通股"}
</Badge>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
)}
{topCirculationShareholders.length > 0 && (
<Box>
<HStack mb={4}>
<Icon as={FaChartLine} color="purple.500" boxSize={5} />
<Heading size="sm">十大流通股东</Heading>
<Badge>
{formatUtils.formatDate(topCirculationShareholders[0].end_date)}
</Badge>
</HStack>
<TableContainer>
<Table size="sm" variant="striped">
<Thead>
<Tr>
<Th>排名</Th>
<Th>股东名称</Th>
<Th>股东类型</Th>
<Th isNumeric>持股数量</Th>
<Th isNumeric>流通股比例</Th>
</Tr>
</Thead>
<Tbody>
{topCirculationShareholders.slice(0, 10).map((shareholder, idx) => (
<Tr key={idx}>
<Td>
<Badge colorScheme={idx < 3 ? "orange" : "gray"}>
{shareholder.shareholder_rank}
</Badge>
</Td>
<Td>
<Tooltip label={shareholder.shareholder_name}>
<Text noOfLines={1} maxW="250px">
{shareholder.shareholder_name}
</Text>
</Tooltip>
</Td>
<Td>
<ShareholderTypeBadge type={shareholder.shareholder_type} />
</Td>
<Td isNumeric fontWeight="medium">
{formatUtils.formatShares(shareholder.holding_shares)}
</Td>
<Td isNumeric>
<Text color="purple.500" fontWeight="bold">
{formatUtils.formatPercentage(
shareholder.circulation_share_ratio
)}
</Text>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
)}
</VStack>
);
};
/**
* 管理团队 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 (
<Center h="200px">
<VStack>
<Spinner size="lg" color="blue.500" />
<Text fontSize="sm" color="gray.500">
加载管理团队数据...
</Text>
</VStack>
</Center>
);
}
return (
<VStack spacing={6} align="stretch">
{Object.entries(getManagementByCategory()).map(
([category, people]) =>
people.length > 0 && (
<Box key={category}>
<HStack mb={4}>
<Icon
as={
category === "高管"
? FaUserTie
: category === "董事"
? FaCrown
: category === "监事"
? FaEye
: FaUsers
}
color={
category === "高管"
? "blue.500"
: category === "董事"
? "purple.500"
: category === "监事"
? "green.500"
: "gray.500"
}
boxSize={5}
/>
<Heading size="sm">{category}</Heading>
<Badge>{people.length}</Badge>
</HStack>
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={4}>
{people.map((person, idx) => (
<Card key={idx} variant="outline" size="sm">
<CardBody>
<HStack spacing={3} align="start">
<Avatar
name={person.name}
size="md"
bg={
category === "高管"
? "blue.500"
: category === "董事"
? "purple.500"
: category === "监事"
? "green.500"
: "gray.500"
}
/>
<VStack align="start" spacing={1} flex={1}>
<HStack>
<Text fontWeight="bold">{person.name}</Text>
{person.gender && (
<Icon
as={FaVenusMars}
color={
person.gender === "男"
? "blue.400"
: "pink.400"
}
boxSize={3}
/>
)}
</HStack>
<Text fontSize="sm" color="blue.600">
{person.position_name}
</Text>
<HStack spacing={2} flexWrap="wrap">
{person.education && (
<Tag size="sm" variant="subtle">
<Icon as={FaGraduationCap} mr={1} boxSize={3} />
{person.education}
</Tag>
)}
{person.birth_year && (
<Tag size="sm" variant="subtle">
{new Date().getFullYear() -
parseInt(person.birth_year)}
</Tag>
)}
{person.nationality &&
person.nationality !== "中国" && (
<Tag size="sm" colorScheme="orange">
<Icon as={FaPassport} mr={1} boxSize={3} />
{person.nationality}
</Tag>
)}
</HStack>
<Text fontSize="xs" color="gray.500">
任职日期{formatUtils.formatDate(person.start_date)}
</Text>
</VStack>
</HStack>
</CardBody>
</Card>
))}
</SimpleGrid>
</Box>
)
)}
</VStack>
);
};
/**
* 公司公告 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 (
<Center h="200px">
<VStack>
<Spinner size="lg" color="blue.500" />
<Text fontSize="sm" color="gray.500">
加载公告数据...
</Text>
</VStack>
</Center>
);
}
return (
<>
<VStack spacing={4} align="stretch">
{disclosureSchedule.length > 0 && (
<Box>
<HStack mb={3}>
<Icon as={FaCalendarAlt} color="orange.500" />
<Text fontWeight="bold">财报披露日程</Text>
</HStack>
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={3}>
{disclosureSchedule.slice(0, 4).map((schedule, idx) => (
<Card
key={idx}
variant="outline"
size="sm"
bg={schedule.is_disclosed ? "green.50" : "orange.50"}
>
<CardBody p={3}>
<VStack spacing={1}>
<Badge
colorScheme={schedule.is_disclosed ? "green" : "orange"}
>
{schedule.report_name}
</Badge>
<Text fontSize="sm" fontWeight="bold">
{schedule.is_disclosed ? "已披露" : "预计"}
</Text>
<Text fontSize="xs">
{formatUtils.formatDate(
schedule.is_disclosed
? schedule.actual_date
: schedule.latest_scheduled_date
)}
</Text>
</VStack>
</CardBody>
</Card>
))}
</SimpleGrid>
</Box>
)}
<Divider />
<Box>
<HStack mb={3}>
<Icon as={FaBullhorn} color="blue.500" />
<Text fontWeight="bold">最新公告</Text>
</HStack>
<VStack spacing={2} align="stretch">
{announcements.map((announcement, idx) => (
<Card
key={idx}
variant="outline"
size="sm"
cursor="pointer"
onClick={() => handleAnnouncementClick(announcement)}
_hover={{ bg: "gray.50" }}
>
<CardBody p={3}>
<HStack justify="space-between">
<VStack align="start" spacing={1} flex={1}>
<HStack>
<Badge size="sm" colorScheme="blue">
{announcement.info_type || "公告"}
</Badge>
<Text fontSize="xs" color="gray.500">
{formatUtils.formatDate(announcement.announce_date)}
</Text>
</HStack>
<Text fontSize="sm" fontWeight="medium" noOfLines={1}>
{announcement.title}
</Text>
</VStack>
<HStack>
{announcement.format && (
<Tag size="sm" variant="subtle">
{announcement.format}
</Tag>
)}
<IconButton
size="sm"
icon={<ExternalLinkIcon />}
variant="ghost"
onClick={(e) => {
e.stopPropagation();
window.open(announcement.url, "_blank");
}}
/>
</HStack>
</HStack>
</CardBody>
</Card>
))}
</VStack>
</Box>
</VStack>
{/* 公告详情模态框 */}
<Modal isOpen={isOpen} onClose={onClose} size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>
<VStack align="start" spacing={1}>
<Text>{selectedAnnouncement?.title}</Text>
<HStack>
<Badge colorScheme="blue">
{selectedAnnouncement?.info_type || "公告"}
</Badge>
<Text fontSize="sm" color="gray.500">
{formatUtils.formatDate(selectedAnnouncement?.announce_date)}
</Text>
</HStack>
</VStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<VStack align="start" spacing={3}>
<Text fontSize="sm">
文件格式{selectedAnnouncement?.format || "-"}
</Text>
<Text fontSize="sm">
文件大小{selectedAnnouncement?.file_size || "-"} KB
</Text>
</VStack>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
mr={3}
onClick={() => window.open(selectedAnnouncement?.url, "_blank")}
>
查看原文
</Button>
<Button variant="ghost" onClick={onClose}>
关闭
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
/**
* 分支机构 Tab Panel - 懒加载子组件
*/
const BranchesTabPanel = ({ stockCode }) => {
const { branches, loading } = useBranchesData(stockCode);
if (loading) {
return (
<Center h="200px">
<VStack>
<Spinner size="lg" color="blue.500" />
<Text fontSize="sm" color="gray.500">
加载分支机构数据...
</Text>
</VStack>
</Center>
);
}
if (branches.length === 0) {
return (
<Center h="200px">
<VStack>
<Icon as={FaSitemap} boxSize={12} color="gray.300" />
<Text color="gray.500">暂无分支机构信息</Text>
</VStack>
</Center>
);
}
return (
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
{branches.map((branch, idx) => (
<Card key={idx} variant="outline">
<CardBody>
<VStack align="start" spacing={3}>
<HStack justify="space-between" w="full">
<Text fontWeight="bold">{branch.branch_name}</Text>
<Badge
colorScheme={
branch.business_status === "存续" ? "green" : "red"
}
>
{branch.business_status}
</Badge>
</HStack>
<SimpleGrid columns={2} spacing={2} w="full">
<VStack align="start" spacing={1}>
<Text fontSize="xs" color="gray.500">
注册资本
</Text>
<Text fontSize="sm" fontWeight="medium">
{branch.register_capital || "-"}
</Text>
</VStack>
<VStack align="start" spacing={1}>
<Text fontSize="xs" color="gray.500">
法人代表
</Text>
<Text fontSize="sm" fontWeight="medium">
{branch.legal_person || "-"}
</Text>
</VStack>
<VStack align="start" spacing={1}>
<Text fontSize="xs" color="gray.500">
成立日期
</Text>
<Text fontSize="sm" fontWeight="medium">
{formatUtils.formatDate(branch.register_date)}
</Text>
</VStack>
<VStack align="start" spacing={1}>
<Text fontSize="xs" color="gray.500">
关联企业
</Text>
<Text fontSize="sm" fontWeight="medium">
{branch.related_company_count || 0}
</Text>
</VStack>
</SimpleGrid>
</VStack>
</CardBody>
</Card>
))}
</SimpleGrid>
);
};
/**
* 工商信息 Tab Panel - 使用父组件传入的 basicInfo
*/
const BusinessInfoTabPanel = ({ basicInfo }) => {
if (!basicInfo) {
return (
<Center h="200px">
<Text color="gray.500">暂无工商信息</Text>
</Center>
);
}
return (
<VStack spacing={4} align="stretch">
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={6}>
<Box>
<Heading size="sm" mb={3}>
工商信息
</Heading>
<VStack align="start" spacing={2}>
<HStack w="full">
<Text fontSize="sm" color="gray.600" minW="80px">
统一信用代码
</Text>
<Code fontSize="xs">{basicInfo.credit_code}</Code>
</HStack>
<HStack w="full">
<Text fontSize="sm" color="gray.600" minW="80px">
公司规模
</Text>
<Text fontSize="sm">{basicInfo.company_size}</Text>
</HStack>
<HStack w="full">
<Text fontSize="sm" color="gray.600" minW="80px">
注册地址
</Text>
<Text fontSize="sm" noOfLines={2}>
{basicInfo.reg_address}
</Text>
</HStack>
<HStack w="full">
<Text fontSize="sm" color="gray.600" minW="80px">
办公地址
</Text>
<Text fontSize="sm" noOfLines={2}>
{basicInfo.office_address}
</Text>
</HStack>
</VStack>
</Box>
<Box>
<Heading size="sm" mb={3}>
服务机构
</Heading>
<VStack align="start" spacing={2}>
<Box>
<Text fontSize="sm" color="gray.600">
会计师事务所
</Text>
<Text fontSize="sm" fontWeight="medium">
{basicInfo.accounting_firm}
</Text>
</Box>
<Box>
<Text fontSize="sm" color="gray.600">
律师事务所
</Text>
<Text fontSize="sm" fontWeight="medium">
{basicInfo.law_firm}
</Text>
</Box>
</VStack>
</Box>
</SimpleGrid>
<Divider />
<Box>
<Heading size="sm" mb={3}>
主营业务
</Heading>
<Text fontSize="sm" lineHeight="tall">
{basicInfo.main_business}
</Text>
</Box>
<Box>
<Heading size="sm" mb={3}>
经营范围
</Heading>
<Text fontSize="sm" lineHeight="tall" color="gray.700">
{basicInfo.business_scope}
</Text>
</Box>
</VStack>
);
};
// ============================================
// 主组件
// ============================================
/**
* 基本信息 Tab 组件(懒加载版本)
*
* Props:
* - stockCode: 股票代码(用于懒加载数据)
* - basicInfo: 公司基本信息(从父组件传入,用于工商信息 Tab
* - cardBg: 卡片背景色
*
* 懒加载策略:
* - 使用 Chakra UI Tabs 的 isLazy 属性
* - 每个 TabPanel 使用独立子组件,在首次激活时才渲染并加载数据
*/
const BasicInfoTab = ({ stockCode, basicInfo, cardBg }) => {
return (
<Card bg={cardBg} shadow="md">
<CardBody>
<Tabs isLazy variant="enclosed" colorScheme="blue">
<TabList flexWrap="wrap">
<Tab>
<Icon as={FaShareAlt} mr={2} />
股权结构
</Tab>
<Tab>
<Icon as={FaUserTie} mr={2} />
管理团队
</Tab>
<Tab>
<Icon as={FaBullhorn} mr={2} />
公司公告
</Tab>
<Tab>
<Icon as={FaSitemap} mr={2} />
分支机构
</Tab>
<Tab>
<Icon as={FaInfoCircle} mr={2} />
工商信息
</Tab>
</TabList>
<TabPanels>
{/* 股权结构 - 懒加载 */}
<TabPanel>
<ShareholderTabPanel stockCode={stockCode} />
</TabPanel>
{/* 管理团队 - 懒加载 */}
<TabPanel>
<ManagementTabPanel stockCode={stockCode} />
</TabPanel>
{/* 公司公告 - 懒加载 */}
<TabPanel>
<AnnouncementsTabPanel stockCode={stockCode} />
</TabPanel>
{/* 分支机构 - 懒加载 */}
<TabPanel>
<BranchesTabPanel stockCode={stockCode} />
</TabPanel>
{/* 工商信息 - 使用父组件传入的 basicInfo */}
<TabPanel>
<BusinessInfoTabPanel basicInfo={basicInfo} />
</TabPanel>
</TabPanels>
</Tabs>
</CardBody>
</Card>
);
};
export default BasicInfoTab;