feat(LoadingState): 新增骨架屏变体,优化加载体验

- LoadingState: 新增 variant 参数支持 spinner/skeleton 模式
- LoadingState: 新增 skeletonType 参数支持 grid/list 布局
- AnnouncementsPanel: 使用 list 骨架屏替代 spinner
- DisclosureSchedulePanel: 使用 grid 骨架屏替代 spinner

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-22 13:02:45 +08:00
parent 77ea38e5c9
commit 174fe32850
3 changed files with 98 additions and 8 deletions

View File

@@ -49,7 +49,7 @@ const AnnouncementsPanel: React.FC<AnnouncementsPanelProps> = ({ stockCode, isAc
};
if (loading) {
return <LoadingState message="加载公告数据..." />;
return <LoadingState variant="skeleton" skeletonType="list" skeletonCount={5} />;
}
return (

View File

@@ -27,7 +27,7 @@ const DisclosureSchedulePanel: React.FC<DisclosureSchedulePanelProps> = ({ stock
const { disclosureSchedule, loading } = useDisclosureData({ stockCode, enabled: isActive });
if (loading) {
return <LoadingState message="加载披露日程..." />;
return <LoadingState variant="skeleton" skeletonType="grid" skeletonCount={4} />;
}
if (disclosureSchedule.length === 0) {

View File

@@ -1,22 +1,110 @@
// src/views/Company/components/CompanyOverview/BasicInfoTab/components/LoadingState.tsx
// 复用的加载状态组件
// 复用的加载状态组件 - 支持骨架屏
import React from "react";
import { Center, VStack, Spinner, Text } from "@chakra-ui/react";
import React, { memo } from "react";
import {
Center,
VStack,
Spinner,
Text,
Box,
Skeleton,
SimpleGrid,
HStack,
} from "@chakra-ui/react";
import { THEME } from "../config";
// 骨架屏颜色配置
const SKELETON_COLORS = {
startColor: "rgba(26, 32, 44, 0.6)",
endColor: "rgba(212, 175, 55, 0.2)",
};
interface LoadingStateProps {
message?: string;
height?: string;
/** 使用骨架屏模式(更好的视觉体验) */
variant?: "spinner" | "skeleton";
/** 骨架屏类型grid网格布局或 list列表布局 */
skeletonType?: "grid" | "list";
/** 骨架屏项目数量 */
skeletonCount?: number;
}
/**
* 加载状态组件(黑金主题
* 网格骨架屏(用于披露日程等
*/
const LoadingState: React.FC<LoadingStateProps> = ({
const GridSkeleton: React.FC<{ count: number }> = memo(({ count }) => (
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={3}>
{Array.from({ length: count }).map((_, i) => (
<Skeleton
key={i}
height="80px"
borderRadius="md"
{...SKELETON_COLORS}
/>
))}
</SimpleGrid>
));
GridSkeleton.displayName = "GridSkeleton";
/**
* 列表骨架屏(用于公告列表等)
*/
const ListSkeleton: React.FC<{ count: number }> = memo(({ count }) => (
<VStack spacing={2} align="stretch">
{Array.from({ length: count }).map((_, i) => (
<Box
key={i}
p={3}
borderRadius="md"
bg="rgba(26, 32, 44, 0.4)"
border="1px solid"
borderColor="rgba(212, 175, 55, 0.2)"
>
<HStack justify="space-between">
<VStack align="start" spacing={2} flex={1}>
<HStack>
<Skeleton height="20px" width="60px" borderRadius="sm" {...SKELETON_COLORS} />
<Skeleton height="16px" width="80px" borderRadius="sm" {...SKELETON_COLORS} />
</HStack>
<Skeleton height="18px" width="90%" borderRadius="sm" {...SKELETON_COLORS} />
</VStack>
<Skeleton height="32px" width="32px" borderRadius="md" {...SKELETON_COLORS} />
</HStack>
</Box>
))}
</VStack>
));
ListSkeleton.displayName = "ListSkeleton";
/**
* 加载状态组件(黑金主题)
*
* @param variant - "spinner"(默认)或 "skeleton"(骨架屏)
* @param skeletonType - 骨架屏类型:"grid" 或 "list"
*/
const LoadingState: React.FC<LoadingStateProps> = memo(({
message = "加载中...",
height = "200px",
variant = "spinner",
skeletonType = "list",
skeletonCount = 4,
}) => {
if (variant === "skeleton") {
return (
<Box minH={height} p={4}>
{skeletonType === "grid" ? (
<GridSkeleton count={skeletonCount} />
) : (
<ListSkeleton count={skeletonCount} />
)}
</Box>
);
}
return (
<Center h={height}>
<VStack>
@@ -27,6 +115,8 @@ const LoadingState: React.FC<LoadingStateProps> = ({
</VStack>
</Center>
);
};
});
LoadingState.displayName = "LoadingState";
export default LoadingState;