feat(BasicInfoTab): 添加骨架屏并适配延迟加载
- 各 Panel 组件适配新的 hooks 参数格式 - 新增 BasicInfoTabSkeleton 骨架屏组件 - 新增 CompanyOverviewNavSkeleton 导航骨架屏组件 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -31,10 +31,14 @@ import LoadingState from "./LoadingState";
|
||||
|
||||
interface AnnouncementsPanelProps {
|
||||
stockCode: string;
|
||||
/** SubTabContainer 传递的激活状态,控制是否加载数据 */
|
||||
isActive?: boolean;
|
||||
/** 激活次数,变化时触发重新请求 */
|
||||
activationKey?: number;
|
||||
}
|
||||
|
||||
const AnnouncementsPanel: React.FC<AnnouncementsPanelProps> = ({ stockCode }) => {
|
||||
const { announcements, loading } = useAnnouncementsData(stockCode);
|
||||
const AnnouncementsPanel: React.FC<AnnouncementsPanelProps> = ({ stockCode, isActive = true, activationKey }) => {
|
||||
const { announcements, loading } = useAnnouncementsData({ stockCode, enabled: isActive, refreshKey: activationKey });
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<any>(null);
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* BasicInfoTab 骨架屏组件
|
||||
* 用于各个 Tab 面板的加载状态显示
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
VStack,
|
||||
HStack,
|
||||
SimpleGrid,
|
||||
Skeleton,
|
||||
SkeletonText,
|
||||
SkeletonCircle,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
// 黑金主题骨架屏样式
|
||||
const skeletonStyles = {
|
||||
startColor: 'rgba(212, 175, 55, 0.1)',
|
||||
endColor: 'rgba(212, 175, 55, 0.2)',
|
||||
};
|
||||
|
||||
// 卡片骨架屏样式
|
||||
const cardStyle = {
|
||||
bg: 'linear-gradient(145deg, rgba(30, 30, 35, 0.95), rgba(20, 20, 25, 0.98))',
|
||||
border: '1px solid',
|
||||
borderColor: 'rgba(212, 175, 55, 0.2)',
|
||||
borderRadius: '12px',
|
||||
p: 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* 分支机构骨架屏
|
||||
*/
|
||||
export const BranchesSkeleton: React.FC = () => (
|
||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<Box key={i} sx={cardStyle}>
|
||||
{/* 顶部金色装饰线 */}
|
||||
<Box
|
||||
h="2px"
|
||||
bgGradient="linear(to-r, transparent, rgba(212, 175, 55, 0.3), transparent)"
|
||||
mb={4}
|
||||
/>
|
||||
<VStack align="start" spacing={4}>
|
||||
{/* 标题行 */}
|
||||
<HStack justify="space-between" w="full">
|
||||
<HStack spacing={2} flex={1}>
|
||||
<Skeleton
|
||||
{...skeletonStyles}
|
||||
height="28px"
|
||||
width="28px"
|
||||
borderRadius="md"
|
||||
/>
|
||||
<Skeleton
|
||||
{...skeletonStyles}
|
||||
height="16px"
|
||||
width="60%"
|
||||
/>
|
||||
</HStack>
|
||||
<Skeleton
|
||||
{...skeletonStyles}
|
||||
height="22px"
|
||||
width="60px"
|
||||
borderRadius="full"
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
{/* 分隔线 */}
|
||||
<Box
|
||||
w="full"
|
||||
h="1px"
|
||||
bgGradient="linear(to-r, rgba(212, 175, 55, 0.2), transparent)"
|
||||
/>
|
||||
|
||||
{/* 信息网格 */}
|
||||
<SimpleGrid columns={2} spacing={3} w="full">
|
||||
{[1, 2, 3, 4].map((j) => (
|
||||
<VStack key={j} align="start" spacing={1}>
|
||||
<Skeleton {...skeletonStyles} height="12px" width="50px" />
|
||||
<Skeleton {...skeletonStyles} height="14px" width="80px" />
|
||||
</VStack>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
);
|
||||
|
||||
/**
|
||||
* 工商信息骨架屏
|
||||
*/
|
||||
export const BusinessInfoSkeleton: React.FC = () => (
|
||||
<VStack spacing={4} align="stretch">
|
||||
{/* 上半部分:工商信息 + 服务机构 */}
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={4}>
|
||||
{/* 工商信息卡片 */}
|
||||
<Box sx={cardStyle}>
|
||||
<HStack spacing={2} mb={4}>
|
||||
<Skeleton {...skeletonStyles} height="16px" width="16px" />
|
||||
<Skeleton {...skeletonStyles} height="16px" width="80px" />
|
||||
</HStack>
|
||||
<VStack spacing={3} align="stretch">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<HStack key={i} spacing={3} p={2}>
|
||||
<Skeleton {...skeletonStyles} height="14px" width="14px" />
|
||||
<Skeleton {...skeletonStyles} height="14px" width="60px" />
|
||||
<Skeleton {...skeletonStyles} height="14px" flex={1} />
|
||||
</HStack>
|
||||
))}
|
||||
</VStack>
|
||||
</Box>
|
||||
|
||||
{/* 服务机构卡片 */}
|
||||
<Box sx={cardStyle}>
|
||||
<HStack spacing={2} mb={4}>
|
||||
<Skeleton {...skeletonStyles} height="16px" width="16px" />
|
||||
<Skeleton {...skeletonStyles} height="16px" width="80px" />
|
||||
</HStack>
|
||||
<VStack spacing={3} align="stretch">
|
||||
{[1, 2].map((i) => (
|
||||
<Box key={i} p={4} borderRadius="10px" bg="rgba(255,255,255,0.02)">
|
||||
<HStack spacing={2} mb={2}>
|
||||
<Skeleton {...skeletonStyles} height="14px" width="14px" />
|
||||
<Skeleton {...skeletonStyles} height="12px" width="80px" />
|
||||
</HStack>
|
||||
<Skeleton {...skeletonStyles} height="14px" width="70%" />
|
||||
</Box>
|
||||
))}
|
||||
</VStack>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
|
||||
{/* 下半部分:主营业务 + 经营范围 */}
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={4}>
|
||||
{[1, 2].map((i) => (
|
||||
<Box key={i} sx={cardStyle}>
|
||||
<HStack spacing={2} mb={4}>
|
||||
<Skeleton {...skeletonStyles} height="16px" width="16px" />
|
||||
<Skeleton {...skeletonStyles} height="16px" width="80px" />
|
||||
</HStack>
|
||||
<SkeletonText
|
||||
{...skeletonStyles}
|
||||
noOfLines={4}
|
||||
spacing={3}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</VStack>
|
||||
);
|
||||
|
||||
/**
|
||||
* 股权结构骨架屏
|
||||
*/
|
||||
export const ShareholderSkeleton: React.FC = () => (
|
||||
<Box p={4}>
|
||||
<VStack spacing={6} align="stretch">
|
||||
{/* 实际控制人 + 股权集中度 */}
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={6}>
|
||||
{[1, 2].map((i) => (
|
||||
<Box key={i} sx={cardStyle}>
|
||||
<HStack spacing={2} mb={4}>
|
||||
<Skeleton {...skeletonStyles} height="18px" width="18px" />
|
||||
<Skeleton {...skeletonStyles} height="18px" width="100px" />
|
||||
</HStack>
|
||||
<VStack spacing={3} align="stretch">
|
||||
{[1, 2, 3].map((j) => (
|
||||
<HStack key={j} justify="space-between">
|
||||
<Skeleton {...skeletonStyles} height="14px" width="80px" />
|
||||
<Skeleton {...skeletonStyles} height="14px" width="60px" />
|
||||
</HStack>
|
||||
))}
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
{/* 十大股东表格 */}
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={6}>
|
||||
{[1, 2].map((i) => (
|
||||
<Box key={i} sx={cardStyle}>
|
||||
<HStack spacing={2} mb={4}>
|
||||
<Skeleton {...skeletonStyles} height="18px" width="18px" />
|
||||
<Skeleton {...skeletonStyles} height="18px" width="100px" />
|
||||
</HStack>
|
||||
<VStack spacing={2} align="stretch">
|
||||
{/* 表头 */}
|
||||
<HStack spacing={4} pb={2} borderBottom="1px solid" borderColor="rgba(212, 175, 55, 0.1)">
|
||||
<Skeleton {...skeletonStyles} height="12px" width="30px" />
|
||||
<Skeleton {...skeletonStyles} height="12px" flex={1} />
|
||||
<Skeleton {...skeletonStyles} height="12px" width="60px" />
|
||||
<Skeleton {...skeletonStyles} height="12px" width="60px" />
|
||||
</HStack>
|
||||
{/* 表格行 */}
|
||||
{[1, 2, 3, 4, 5].map((j) => (
|
||||
<HStack key={j} spacing={4} py={2}>
|
||||
<SkeletonCircle {...skeletonStyles} size="6" />
|
||||
<Skeleton {...skeletonStyles} height="14px" flex={1} />
|
||||
<Skeleton {...skeletonStyles} height="14px" width="60px" />
|
||||
<Skeleton {...skeletonStyles} height="14px" width="60px" />
|
||||
</HStack>
|
||||
))}
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/**
|
||||
* 管理团队骨架屏
|
||||
*/
|
||||
export const ManagementSkeleton: React.FC = () => (
|
||||
<Box p={4}>
|
||||
<VStack spacing={6} align="stretch">
|
||||
{/* 每个分类 */}
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Box key={i}>
|
||||
{/* 分类标题 */}
|
||||
<HStack spacing={2} mb={4}>
|
||||
<Skeleton {...skeletonStyles} height="20px" width="20px" />
|
||||
<Skeleton {...skeletonStyles} height="18px" width="80px" />
|
||||
<Skeleton
|
||||
{...skeletonStyles}
|
||||
height="20px"
|
||||
width="30px"
|
||||
borderRadius="full"
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
{/* 人员卡片网格 */}
|
||||
<SimpleGrid columns={{ base: 2, md: 3, lg: 4 }} spacing={4}>
|
||||
{[1, 2, 3, 4].map((j) => (
|
||||
<Box key={j} sx={cardStyle}>
|
||||
<VStack spacing={3}>
|
||||
<SkeletonCircle {...skeletonStyles} size="12" />
|
||||
<Skeleton {...skeletonStyles} height="16px" width="60px" />
|
||||
<Skeleton {...skeletonStyles} height="12px" width="80px" />
|
||||
<HStack spacing={2}>
|
||||
<Skeleton {...skeletonStyles} height="10px" width="40px" />
|
||||
<Skeleton {...skeletonStyles} height="10px" width="40px" />
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
))}
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/**
|
||||
* 通用内容骨架屏
|
||||
*/
|
||||
export const ContentSkeleton: React.FC = () => (
|
||||
<Box p={4}>
|
||||
<SkeletonText {...skeletonStyles} noOfLines={6} spacing={4} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
export default {
|
||||
BranchesSkeleton,
|
||||
BusinessInfoSkeleton,
|
||||
ShareholderSkeleton,
|
||||
ManagementSkeleton,
|
||||
ContentSkeleton,
|
||||
};
|
||||
@@ -16,10 +16,12 @@ import { FaSitemap, FaBuilding, FaCheckCircle, FaTimesCircle } from "react-icons
|
||||
import { useBranchesData } from "../../hooks/useBranchesData";
|
||||
import { THEME } from "../config";
|
||||
import { formatDate } from "../utils";
|
||||
import LoadingState from "./LoadingState";
|
||||
import { BranchesSkeleton } from "./BasicInfoTabSkeleton";
|
||||
|
||||
interface BranchesPanelProps {
|
||||
stockCode: string;
|
||||
/** SubTabContainer 传递的激活状态,控制是否加载数据 */
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
// 黑金卡片样式
|
||||
@@ -65,11 +67,11 @@ const InfoItem: React.FC<{ label: string; value: string | number }> = ({ label,
|
||||
</VStack>
|
||||
);
|
||||
|
||||
const BranchesPanel: React.FC<BranchesPanelProps> = ({ stockCode }) => {
|
||||
const { branches, loading } = useBranchesData(stockCode);
|
||||
const BranchesPanel: React.FC<BranchesPanelProps> = ({ stockCode, isActive = true }) => {
|
||||
const { branches, loading } = useBranchesData({ stockCode, enabled: isActive });
|
||||
|
||||
if (loading) {
|
||||
return <LoadingState message="加载分支机构数据..." />;
|
||||
return <BranchesSkeleton />;
|
||||
}
|
||||
|
||||
if (branches.length === 0) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
SimpleGrid,
|
||||
Center,
|
||||
Icon,
|
||||
Spinner,
|
||||
} from "@chakra-ui/react";
|
||||
import {
|
||||
FaBuilding,
|
||||
@@ -27,9 +26,12 @@ import {
|
||||
import { COLORS, GLASS, glassCardStyle } from "@views/Company/theme";
|
||||
import { THEME } from "../config";
|
||||
import { useBasicInfo } from "../../hooks/useBasicInfo";
|
||||
import { BusinessInfoSkeleton } from "./BasicInfoTabSkeleton";
|
||||
|
||||
interface BusinessInfoPanelProps {
|
||||
stockCode: string;
|
||||
/** SubTabContainer 传递的激活状态,控制是否加载数据 */
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
// 区块标题组件
|
||||
@@ -150,15 +152,11 @@ const TextSection: React.FC<{
|
||||
</Box>
|
||||
);
|
||||
|
||||
const BusinessInfoPanel: React.FC<BusinessInfoPanelProps> = ({ stockCode }) => {
|
||||
const { basicInfo, loading } = useBasicInfo(stockCode);
|
||||
const BusinessInfoPanel: React.FC<BusinessInfoPanelProps> = ({ stockCode, isActive = true }) => {
|
||||
const { basicInfo, loading } = useBasicInfo({ stockCode, enabled: isActive });
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Center h="200px">
|
||||
<Spinner size="lg" color={THEME.gold} />
|
||||
</Center>
|
||||
);
|
||||
return <BusinessInfoSkeleton />;
|
||||
}
|
||||
|
||||
if (!basicInfo) {
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* 公司概览 - 导航骨架屏组件
|
||||
*
|
||||
* 用于懒加载时显示,让二级导航立即可见
|
||||
* 导航使用真实 UI,内容区域显示骨架屏
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
HStack,
|
||||
Text,
|
||||
Icon,
|
||||
Skeleton,
|
||||
VStack,
|
||||
Card,
|
||||
CardBody,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
FaShareAlt,
|
||||
FaUserTie,
|
||||
FaSitemap,
|
||||
FaInfoCircle,
|
||||
} from 'react-icons/fa';
|
||||
|
||||
// 深空 FUI 主题配置(与 SubTabContainer 保持一致)
|
||||
const DEEP_SPACE = {
|
||||
bgGlass: 'rgba(12, 14, 28, 0.6)',
|
||||
borderGold: 'rgba(212, 175, 55, 0.2)',
|
||||
borderGoldHover: 'rgba(212, 175, 55, 0.5)',
|
||||
glowGold: '0 0 30px rgba(212, 175, 55, 0.25), 0 4px 20px rgba(0, 0, 0, 0.3)',
|
||||
innerGlow: 'inset 0 1px 0 rgba(255, 255, 255, 0.08)',
|
||||
textWhite: 'rgba(255, 255, 255, 0.95)',
|
||||
textDark: '#0A0A14',
|
||||
selectedBg: 'linear-gradient(135deg, rgba(212, 175, 55, 0.95) 0%, rgba(184, 150, 12, 0.95) 100%)',
|
||||
radius: '12px',
|
||||
radiusLG: '16px',
|
||||
};
|
||||
|
||||
// 导航配置(与主组件 config.ts 保持同步)
|
||||
const OVERVIEW_TABS = [
|
||||
{ key: 'shareholder', name: '股权结构', icon: FaShareAlt },
|
||||
{ key: 'management', name: '管理团队', icon: FaUserTie },
|
||||
{ key: 'branches', name: '分支机构', icon: FaSitemap },
|
||||
{ key: 'business', name: '工商信息', icon: FaInfoCircle },
|
||||
];
|
||||
|
||||
/**
|
||||
* 股权结构内容骨架屏
|
||||
*/
|
||||
const ShareholderContentSkeleton: React.FC = () => (
|
||||
<Box p={4}>
|
||||
{/* 表格骨架屏 */}
|
||||
<Card bg="gray.900" border="1px solid" borderColor="rgba(212, 175, 55, 0.2)">
|
||||
<CardBody p={0}>
|
||||
<Table variant="simple" size="sm">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderColor="rgba(212, 175, 55, 0.2)" color="gray.400">
|
||||
<Skeleton height="14px" width="60px" startColor="gray.700" endColor="gray.600" />
|
||||
</Th>
|
||||
<Th borderColor="rgba(212, 175, 55, 0.2)" color="gray.400">
|
||||
<Skeleton height="14px" width="80px" startColor="gray.700" endColor="gray.600" />
|
||||
</Th>
|
||||
<Th borderColor="rgba(212, 175, 55, 0.2)" color="gray.400">
|
||||
<Skeleton height="14px" width="60px" startColor="gray.700" endColor="gray.600" />
|
||||
</Th>
|
||||
<Th borderColor="rgba(212, 175, 55, 0.2)" color="gray.400">
|
||||
<Skeleton height="14px" width="70px" startColor="gray.700" endColor="gray.600" />
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<Tr key={i}>
|
||||
<Td borderColor="rgba(212, 175, 55, 0.2)">
|
||||
<Skeleton height="14px" width="120px" startColor="gray.700" endColor="gray.600" />
|
||||
</Td>
|
||||
<Td borderColor="rgba(212, 175, 55, 0.2)">
|
||||
<Skeleton height="14px" width="80px" startColor="gray.700" endColor="gray.600" />
|
||||
</Td>
|
||||
<Td borderColor="rgba(212, 175, 55, 0.2)">
|
||||
<Skeleton height="14px" width="60px" startColor="gray.700" endColor="gray.600" />
|
||||
</Td>
|
||||
<Td borderColor="rgba(212, 175, 55, 0.2)">
|
||||
<Skeleton height="14px" width="80px" startColor="gray.700" endColor="gray.600" />
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/**
|
||||
* CompanyOverview 导航骨架屏
|
||||
*
|
||||
* 显示真实的导航 Tab(默认选中第一个),内容区域显示骨架屏
|
||||
*/
|
||||
const CompanyOverviewNavSkeleton: React.FC = () => {
|
||||
return (
|
||||
<Box>
|
||||
{/* 导航栏容器 - compact 模式(无外边距) */}
|
||||
<Flex
|
||||
bg={DEEP_SPACE.bgGlass}
|
||||
backdropFilter="blur(20px)"
|
||||
borderBottom="1px solid"
|
||||
borderColor={DEEP_SPACE.borderGold}
|
||||
borderRadius={0}
|
||||
mx={0}
|
||||
mb={0}
|
||||
position="relative"
|
||||
boxShadow="none"
|
||||
alignItems="center"
|
||||
>
|
||||
{/* 顶部金色光条 */}
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left="50%"
|
||||
transform="translateX(-50%)"
|
||||
width="50%"
|
||||
height="1px"
|
||||
background="linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.4), transparent)"
|
||||
/>
|
||||
|
||||
{/* Tab 列表 */}
|
||||
<Box
|
||||
flex="1"
|
||||
minW={0}
|
||||
overflowX="auto"
|
||||
css={{
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
scrollbarWidth: 'none',
|
||||
}}
|
||||
>
|
||||
<HStack
|
||||
border="none"
|
||||
px={3}
|
||||
py={2}
|
||||
flexWrap="nowrap"
|
||||
gap={1.5}
|
||||
>
|
||||
{OVERVIEW_TABS.map((tab, idx) => {
|
||||
const isSelected = idx === 0;
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={tab.key}
|
||||
color={isSelected ? DEEP_SPACE.textDark : DEEP_SPACE.textWhite}
|
||||
borderRadius={DEEP_SPACE.radius}
|
||||
px={4}
|
||||
py={2}
|
||||
fontSize="13px"
|
||||
fontWeight={isSelected ? '700' : '500'}
|
||||
whiteSpace="nowrap"
|
||||
flexShrink={0}
|
||||
border="1px solid"
|
||||
borderColor={isSelected ? DEEP_SPACE.borderGoldHover : 'transparent'}
|
||||
position="relative"
|
||||
letterSpacing="0.03em"
|
||||
bg={isSelected ? DEEP_SPACE.selectedBg : 'transparent'}
|
||||
boxShadow={isSelected ? DEEP_SPACE.glowGold : 'none'}
|
||||
transform={isSelected ? 'translateY(-2px)' : 'none'}
|
||||
cursor="default"
|
||||
>
|
||||
<HStack spacing={1.5}>
|
||||
<Icon
|
||||
as={tab.icon}
|
||||
boxSize={3.5}
|
||||
opacity={isSelected ? 1 : 0.7}
|
||||
/>
|
||||
<Text>{tab.name}</Text>
|
||||
</HStack>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{/* 内容区域骨架屏 */}
|
||||
<ShareholderContentSkeleton />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyOverviewNavSkeleton;
|
||||
@@ -19,10 +19,12 @@ import LoadingState from "./LoadingState";
|
||||
|
||||
interface DisclosureSchedulePanelProps {
|
||||
stockCode: string;
|
||||
/** SubTabContainer 传递的激活状态,控制是否加载数据 */
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
const DisclosureSchedulePanel: React.FC<DisclosureSchedulePanelProps> = ({ stockCode }) => {
|
||||
const { disclosureSchedule, loading } = useDisclosureData(stockCode);
|
||||
const DisclosureSchedulePanel: React.FC<DisclosureSchedulePanelProps> = ({ stockCode, isActive = true }) => {
|
||||
const { disclosureSchedule, loading } = useDisclosureData({ stockCode, enabled: isActive });
|
||||
|
||||
if (loading) {
|
||||
return <LoadingState message="加载披露日程..." />;
|
||||
|
||||
@@ -11,9 +11,12 @@ import {
|
||||
ShareholdersTable,
|
||||
} from "../../components/shareholder";
|
||||
import TabPanelContainer from "@components/TabPanelContainer";
|
||||
import { ShareholderSkeleton } from "./BasicInfoTabSkeleton";
|
||||
|
||||
interface ShareholderPanelProps {
|
||||
stockCode: string;
|
||||
/** SubTabContainer 传递的激活状态,控制是否加载数据 */
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,17 +26,17 @@ interface ShareholderPanelProps {
|
||||
* - ConcentrationCard: 股权集中度卡片
|
||||
* - ShareholdersTable: 股东表格(合并版,支持十大股东和十大流通股东)
|
||||
*/
|
||||
const ShareholderPanel: React.FC<ShareholderPanelProps> = ({ stockCode }) => {
|
||||
const ShareholderPanel: React.FC<ShareholderPanelProps> = ({ stockCode, isActive = true }) => {
|
||||
const {
|
||||
actualControl,
|
||||
concentration,
|
||||
topShareholders,
|
||||
topCirculationShareholders,
|
||||
loading,
|
||||
} = useShareholderData(stockCode);
|
||||
} = useShareholderData({ stockCode, enabled: isActive });
|
||||
|
||||
return (
|
||||
<TabPanelContainer loading={loading} loadingMessage="加载股权结构数据...">
|
||||
<TabPanelContainer loading={loading} skeleton={<ShareholderSkeleton />}>
|
||||
{/* 实际控制人 + 股权集中度 左右分布 */}
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={6}>
|
||||
<Box>
|
||||
|
||||
@@ -9,3 +9,6 @@ export { ManagementPanel } from "./management";
|
||||
export { default as AnnouncementsPanel } from "./AnnouncementsPanel";
|
||||
export { default as BranchesPanel } from "./BranchesPanel";
|
||||
export { default as BusinessInfoPanel } from "./BusinessInfoPanel";
|
||||
|
||||
// 骨架屏组件
|
||||
export * from "./BasicInfoTabSkeleton";
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useManagementData } from "../../../hooks/useManagementData";
|
||||
import { THEME } from "../../config";
|
||||
import TabPanelContainer from "@components/TabPanelContainer";
|
||||
import CategorySection from "./CategorySection";
|
||||
import { ManagementSkeleton } from "../BasicInfoTabSkeleton";
|
||||
import type {
|
||||
ManagementPerson,
|
||||
ManagementCategory,
|
||||
@@ -22,6 +23,8 @@ import type {
|
||||
|
||||
interface ManagementPanelProps {
|
||||
stockCode: string;
|
||||
/** SubTabContainer 传递的激活状态,控制是否加载数据 */
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,8 +71,8 @@ const categorizeManagement = (management: ManagementPerson[]): CategorizedManage
|
||||
return categories;
|
||||
};
|
||||
|
||||
const ManagementPanel: React.FC<ManagementPanelProps> = ({ stockCode }) => {
|
||||
const { management, loading } = useManagementData(stockCode);
|
||||
const ManagementPanel: React.FC<ManagementPanelProps> = ({ stockCode, isActive = true }) => {
|
||||
const { management, loading } = useManagementData({ stockCode, enabled: isActive });
|
||||
|
||||
// 使用 useMemo 缓存分类计算结果
|
||||
const categorizedManagement = useMemo(
|
||||
@@ -78,7 +81,7 @@ const ManagementPanel: React.FC<ManagementPanelProps> = ({ stockCode }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<TabPanelContainer loading={loading} loadingMessage="加载管理团队数据...">
|
||||
<TabPanelContainer loading={loading} skeleton={<ManagementSkeleton />}>
|
||||
{CATEGORY_ORDER.map((category) => {
|
||||
const config = CATEGORY_CONFIG[category];
|
||||
const people = categorizedManagement[category];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 基本信息 Tab 组件 - 使用 SubTabContainer 通用组件
|
||||
|
||||
import React, { useMemo } from "react";
|
||||
import { Card, CardBody } from "@chakra-ui/react";
|
||||
import SubTabContainer, { type SubTabConfig } from "@components/SubTabContainer";
|
||||
|
||||
import { THEME, TAB_CONFIG, getEnabledTabs } from "./config";
|
||||
@@ -65,16 +66,18 @@ const BasicInfoTab: React.FC<BasicInfoTabProps> = ({
|
||||
const tabs = useMemo(() => buildTabsConfig(enabledTabs), [enabledTabs]);
|
||||
|
||||
return (
|
||||
<SubTabContainer
|
||||
tabs={tabs}
|
||||
componentProps={{ stockCode }}
|
||||
defaultIndex={defaultTabIndex}
|
||||
onTabChange={onTabChange}
|
||||
themePreset="blackGold"
|
||||
compact
|
||||
size="sm"
|
||||
contentPadding={0}
|
||||
/>
|
||||
<Card bg="gray.900" shadow="md" border="1px solid" borderColor="rgba(212, 175, 55, 0.3)">
|
||||
<CardBody p={0}>
|
||||
<SubTabContainer
|
||||
tabs={tabs}
|
||||
componentProps={{ stockCode }}
|
||||
defaultIndex={defaultTabIndex}
|
||||
onTabChange={onTabChange}
|
||||
themePreset="blackGold"
|
||||
size="sm"
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user