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:
@@ -49,7 +49,7 @@ const AnnouncementsPanel: React.FC<AnnouncementsPanelProps> = ({ stockCode, isAc
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingState message="加载公告数据..." />;
|
return <LoadingState variant="skeleton" skeletonType="list" skeletonCount={5} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const DisclosureSchedulePanel: React.FC<DisclosureSchedulePanelProps> = ({ stock
|
|||||||
const { disclosureSchedule, loading } = useDisclosureData({ stockCode, enabled: isActive });
|
const { disclosureSchedule, loading } = useDisclosureData({ stockCode, enabled: isActive });
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingState message="加载披露日程..." />;
|
return <LoadingState variant="skeleton" skeletonType="grid" skeletonCount={4} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disclosureSchedule.length === 0) {
|
if (disclosureSchedule.length === 0) {
|
||||||
|
|||||||
@@ -1,22 +1,110 @@
|
|||||||
// src/views/Company/components/CompanyOverview/BasicInfoTab/components/LoadingState.tsx
|
// src/views/Company/components/CompanyOverview/BasicInfoTab/components/LoadingState.tsx
|
||||||
// 复用的加载状态组件
|
// 复用的加载状态组件 - 支持骨架屏
|
||||||
|
|
||||||
import React from "react";
|
import React, { memo } from "react";
|
||||||
import { Center, VStack, Spinner, Text } from "@chakra-ui/react";
|
import {
|
||||||
|
Center,
|
||||||
|
VStack,
|
||||||
|
Spinner,
|
||||||
|
Text,
|
||||||
|
Box,
|
||||||
|
Skeleton,
|
||||||
|
SimpleGrid,
|
||||||
|
HStack,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
import { THEME } from "../config";
|
import { THEME } from "../config";
|
||||||
|
|
||||||
|
// 骨架屏颜色配置
|
||||||
|
const SKELETON_COLORS = {
|
||||||
|
startColor: "rgba(26, 32, 44, 0.6)",
|
||||||
|
endColor: "rgba(212, 175, 55, 0.2)",
|
||||||
|
};
|
||||||
|
|
||||||
interface LoadingStateProps {
|
interface LoadingStateProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
height?: 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 = "加载中...",
|
message = "加载中...",
|
||||||
height = "200px",
|
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 (
|
return (
|
||||||
<Center h={height}>
|
<Center h={height}>
|
||||||
<VStack>
|
<VStack>
|
||||||
@@ -27,6 +115,8 @@ const LoadingState: React.FC<LoadingStateProps> = ({
|
|||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
LoadingState.displayName = "LoadingState";
|
||||||
|
|
||||||
export default LoadingState;
|
export default LoadingState;
|
||||||
|
|||||||
Reference in New Issue
Block a user