pref: 权限校验中 - 显示占位骨架,不显示登录按钮或用户菜单,/home页面添加骨架屏逻辑
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
// Navbar 右侧功能区组件
|
// Navbar 右侧功能区组件
|
||||||
|
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { HStack, Spinner, IconButton, Box } from '@chakra-ui/react';
|
import { HStack, IconButton, Box } from '@chakra-ui/react';
|
||||||
import { HamburgerIcon } from '@chakra-ui/icons';
|
import { HamburgerIcon } from '@chakra-ui/icons';
|
||||||
// import ThemeToggleButton from '../ThemeToggleButton'; // ❌ 已删除 - 不再支持深色模式切换
|
// import ThemeToggleButton from '../ThemeToggleButton'; // ❌ 已删除 - 不再支持深色模式切换
|
||||||
import LoginButton from '../LoginButton';
|
import LoginButton from '../LoginButton';
|
||||||
@@ -41,9 +41,15 @@ const NavbarActions = memo(({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<HStack spacing={{ base: 2, md: 4 }}>
|
<HStack spacing={{ base: 2, md: 4 }}>
|
||||||
{/* 显示加载状态 */}
|
{/* 权限校验中 - 显示占位骨架,不显示登录按钮或用户菜单 */}
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Spinner size="sm" color="blue.500" />
|
<Box
|
||||||
|
w={{ base: '80px', md: '120px' }}
|
||||||
|
h="36px"
|
||||||
|
borderRadius="md"
|
||||||
|
bg="whiteAlpha.100"
|
||||||
|
opacity={0.6}
|
||||||
|
/>
|
||||||
) : isAuthenticated && user ? (
|
) : isAuthenticated && user ? (
|
||||||
// 已登录状态 - 用户菜单 + 功能菜单排列
|
// 已登录状态 - 用户菜单 + 功能菜单排列
|
||||||
<HStack spacing={{ base: 2, md: 3 }}>
|
<HStack spacing={{ base: 2, md: 3 }}>
|
||||||
|
|||||||
@@ -9,43 +9,44 @@ import React from 'react';
|
|||||||
*/
|
*/
|
||||||
export const lazyComponents = {
|
export const lazyComponents = {
|
||||||
// Home 模块
|
// Home 模块
|
||||||
HomePage: React.lazy(() => import('../views/Home/HomePage')),
|
// 首页使用专用入口组件,内置骨架屏 fallback
|
||||||
CenterDashboard: React.lazy(() => import('../views/Dashboard/Center')),
|
HomePage: React.lazy(() => import('@views/Home')),
|
||||||
ProfilePage: React.lazy(() => import('../views/Profile')),
|
CenterDashboard: React.lazy(() => import('@views/Dashboard/Center')),
|
||||||
SettingsPage: React.lazy(() => import('../views/Settings/SettingsPage')),
|
ProfilePage: React.lazy(() => import('@views/Profile')),
|
||||||
Subscription: React.lazy(() => import('../views/Pages/Account/Subscription')),
|
SettingsPage: React.lazy(() => import('@views/Settings/SettingsPage')),
|
||||||
PrivacyPolicy: React.lazy(() => import('../views/Pages/PrivacyPolicy')),
|
Subscription: React.lazy(() => import('@views/Pages/Account/Subscription')),
|
||||||
UserAgreement: React.lazy(() => import('../views/Pages/UserAgreement')),
|
PrivacyPolicy: React.lazy(() => import('@views/Pages/PrivacyPolicy')),
|
||||||
WechatCallback: React.lazy(() => import('../views/Pages/WechatCallback')),
|
UserAgreement: React.lazy(() => import('@views/Pages/UserAgreement')),
|
||||||
|
WechatCallback: React.lazy(() => import('@views/Pages/WechatCallback')),
|
||||||
|
|
||||||
// 社区/内容模块
|
// 社区/内容模块
|
||||||
Community: React.lazy(() => import('../views/Community')),
|
Community: React.lazy(() => import('@views/Community')),
|
||||||
ConceptCenter: React.lazy(() => import('../views/Concept')),
|
ConceptCenter: React.lazy(() => import('@views/Concept')),
|
||||||
StockOverview: React.lazy(() => import('../views/StockOverview')),
|
StockOverview: React.lazy(() => import('@views/StockOverview')),
|
||||||
LimitAnalyse: React.lazy(() => import('../views/LimitAnalyse')),
|
LimitAnalyse: React.lazy(() => import('@views/LimitAnalyse')),
|
||||||
|
|
||||||
// 交易模块
|
// 交易模块
|
||||||
TradingSimulation: React.lazy(() => import('../views/TradingSimulation')),
|
TradingSimulation: React.lazy(() => import('@views/TradingSimulation')),
|
||||||
|
|
||||||
// 事件模块
|
// 事件模块
|
||||||
EventDetail: React.lazy(() => import('../views/EventDetail')),
|
EventDetail: React.lazy(() => import('@views/EventDetail')),
|
||||||
|
|
||||||
// 公司相关模块
|
// 公司相关模块
|
||||||
CompanyIndex: React.lazy(() => import('../views/Company')),
|
CompanyIndex: React.lazy(() => import('@views/Company')),
|
||||||
ForecastReport: React.lazy(() => import('../views/Company/ForecastReport')),
|
ForecastReport: React.lazy(() => import('@views/Company/ForecastReport')),
|
||||||
FinancialPanorama: React.lazy(() => import('../views/Company/FinancialPanorama')),
|
FinancialPanorama: React.lazy(() => import('@views/Company/FinancialPanorama')),
|
||||||
MarketDataView: React.lazy(() => import('../views/Company/MarketDataView')),
|
MarketDataView: React.lazy(() => import('@views/Company/MarketDataView')),
|
||||||
|
|
||||||
// Agent模块
|
// Agent模块
|
||||||
AgentChat: React.lazy(() => import('../views/AgentChat')),
|
AgentChat: React.lazy(() => import('@views/AgentChat')),
|
||||||
|
|
||||||
// 价值论坛模块
|
// 价值论坛模块
|
||||||
ValueForum: React.lazy(() => import('../views/ValueForum')),
|
ValueForum: React.lazy(() => import('@views/ValueForum')),
|
||||||
ForumPostDetail: React.lazy(() => import('../views/ValueForum/PostDetail')),
|
ForumPostDetail: React.lazy(() => import('@views/ValueForum/PostDetail')),
|
||||||
PredictionTopicDetail: React.lazy(() => import('../views/ValueForum/PredictionTopicDetail')),
|
PredictionTopicDetail: React.lazy(() => import('@views/ValueForum/PredictionTopicDetail')),
|
||||||
|
|
||||||
// 数据浏览器模块
|
// 数据浏览器模块
|
||||||
DataBrowser: React.lazy(() => import('../views/DataBrowser')),
|
DataBrowser: React.lazy(() => import('@views/DataBrowser')),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* 首页骨架屏组件
|
* 首页骨架屏组件
|
||||||
* 模拟首页的 6 个功能卡片布局,减少白屏感知时间
|
* 匹配首页深色科技风格,减少白屏感知时间
|
||||||
*
|
|
||||||
* 使用 Chakra UI 的 Skeleton 组件
|
|
||||||
*
|
*
|
||||||
* @module views/Home/components/HomePageSkeleton
|
* @module views/Home/components/HomePageSkeleton
|
||||||
*/
|
*/
|
||||||
@@ -15,8 +13,6 @@ import {
|
|||||||
Skeleton,
|
Skeleton,
|
||||||
SkeletonText,
|
SkeletonText,
|
||||||
VStack,
|
VStack,
|
||||||
HStack,
|
|
||||||
useColorModeValue,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -24,163 +20,152 @@ import {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
interface HomePageSkeletonProps {
|
interface HomePageSkeletonProps {
|
||||||
/** 是否显示动画效果 */
|
|
||||||
isAnimated?: boolean;
|
|
||||||
/** 骨架屏速度(秒) */
|
/** 骨架屏速度(秒) */
|
||||||
speed?: number;
|
speed?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 单个卡片骨架
|
// 单个卡片骨架 - 深色风格
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
const FeatureCardSkeleton: React.FC<{ isFeatured?: boolean }> = ({ isFeatured = false }) => {
|
const FeatureCardSkeleton: React.FC<{ isFeatured?: boolean; speed?: number }> = ({
|
||||||
const bg = useColorModeValue('white', 'gray.800');
|
isFeatured = false,
|
||||||
const borderColor = useColorModeValue('gray.200', 'gray.700');
|
speed = 0.8,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
bg={bg}
|
bg="rgba(255, 255, 255, 0.03)"
|
||||||
borderRadius="xl"
|
borderRadius="2xl"
|
||||||
borderWidth="1px"
|
borderWidth="1px"
|
||||||
borderColor={borderColor}
|
borderColor="whiteAlpha.100"
|
||||||
p={isFeatured ? 8 : 6}
|
p={{ base: 5, md: isFeatured ? 8 : 6 }}
|
||||||
h={isFeatured ? '350px' : '280px'}
|
h={isFeatured ? { base: '280px', md: '320px' } : { base: '200px', md: '240px' }}
|
||||||
boxShadow={isFeatured ? 'xl' : 'md'}
|
backdropFilter="blur(10px)"
|
||||||
position="relative"
|
|
||||||
>
|
>
|
||||||
<VStack align="start" spacing={4} h="full">
|
<VStack align="start" spacing={4} h="full">
|
||||||
{/* 图标骨架 */}
|
{/* 图标骨架 */}
|
||||||
<Skeleton
|
<Skeleton
|
||||||
height={isFeatured ? '60px' : '48px'}
|
height={isFeatured ? '56px' : '44px'}
|
||||||
width={isFeatured ? '60px' : '48px'}
|
width={isFeatured ? '56px' : '44px'}
|
||||||
borderRadius="lg"
|
borderRadius="xl"
|
||||||
startColor={isFeatured ? 'blue.100' : 'gray.100'}
|
startColor="whiteAlpha.100"
|
||||||
endColor={isFeatured ? 'blue.200' : 'gray.200'}
|
endColor="whiteAlpha.200"
|
||||||
|
speed={speed}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 标题骨架 */}
|
{/* 标题骨架 */}
|
||||||
<Skeleton height="28px" width="70%" borderRadius="md" />
|
<Skeleton
|
||||||
|
height="24px"
|
||||||
|
width="60%"
|
||||||
|
borderRadius="md"
|
||||||
|
startColor="whiteAlpha.100"
|
||||||
|
endColor="whiteAlpha.200"
|
||||||
|
speed={speed}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 描述骨架 */}
|
{/* 描述骨架 */}
|
||||||
<SkeletonText
|
<SkeletonText
|
||||||
mt="2"
|
mt="2"
|
||||||
noOfLines={isFeatured ? 4 : 3}
|
noOfLines={isFeatured ? 3 : 2}
|
||||||
spacing="3"
|
spacing="3"
|
||||||
skeletonHeight="2"
|
skeletonHeight="3"
|
||||||
width="100%"
|
width="90%"
|
||||||
/>
|
startColor="whiteAlpha.50"
|
||||||
|
endColor="whiteAlpha.100"
|
||||||
{/* 按钮骨架 */}
|
speed={speed}
|
||||||
<Skeleton
|
|
||||||
height="40px"
|
|
||||||
width={isFeatured ? '140px' : '100px'}
|
|
||||||
borderRadius="md"
|
|
||||||
mt="auto"
|
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|
||||||
{/* Featured 徽章骨架 */}
|
|
||||||
{isFeatured && (
|
|
||||||
<Skeleton
|
|
||||||
position="absolute"
|
|
||||||
top="4"
|
|
||||||
right="4"
|
|
||||||
height="24px"
|
|
||||||
width="80px"
|
|
||||||
borderRadius="full"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Hero 区域骨架
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const HeroSkeleton: React.FC<{ speed?: number }> = ({ speed = 0.8 }) => {
|
||||||
|
return (
|
||||||
|
<VStack spacing={{ base: 4, md: 6 }} align="center" py={{ base: 8, md: 12 }}>
|
||||||
|
{/* 主标题骨架 */}
|
||||||
|
<Skeleton
|
||||||
|
height={{ base: '48px', md: '72px' }}
|
||||||
|
width={{ base: '280px', md: '400px' }}
|
||||||
|
borderRadius="lg"
|
||||||
|
startColor="whiteAlpha.100"
|
||||||
|
endColor="whiteAlpha.200"
|
||||||
|
speed={speed}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 副标题骨架 */}
|
||||||
|
<Skeleton
|
||||||
|
height={{ base: '20px', md: '24px' }}
|
||||||
|
width={{ base: '200px', md: '300px' }}
|
||||||
|
borderRadius="md"
|
||||||
|
startColor="whiteAlpha.50"
|
||||||
|
endColor="whiteAlpha.100"
|
||||||
|
speed={speed}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 主骨架组件
|
// 主骨架组件
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
export const HomePageSkeleton: React.FC<HomePageSkeletonProps> = ({
|
export const HomePageSkeleton: React.FC<HomePageSkeletonProps> = ({
|
||||||
isAnimated = true,
|
|
||||||
speed = 0.8,
|
speed = 0.8,
|
||||||
}) => {
|
}) => {
|
||||||
const containerBg = useColorModeValue('gray.50', 'gray.900');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
w="full"
|
w="full"
|
||||||
minH="100vh"
|
minH="100vh"
|
||||||
bg={containerBg}
|
bg="linear-gradient(135deg, #0E0C15 0%, #15131D 50%, #252134 100%)"
|
||||||
pt={{ base: '120px', md: '140px' }}
|
position="relative"
|
||||||
pb={{ base: '60px', md: '80px' }}
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
<Container maxW="container.xl">
|
{/* 背景装饰 - 模拟光晕效果 */}
|
||||||
<VStack spacing={{ base: 8, md: 12 }} align="stretch">
|
<Box
|
||||||
{/* 顶部标题区域骨架 */}
|
position="absolute"
|
||||||
<VStack spacing={4} textAlign="center">
|
top="-20%"
|
||||||
{/* 主标题 */}
|
left="50%"
|
||||||
<Skeleton
|
transform="translateX(-50%)"
|
||||||
height={{ base: '40px', md: '56px' }}
|
w="800px"
|
||||||
width={{ base: '80%', md: '500px' }}
|
h="800px"
|
||||||
borderRadius="md"
|
bg="radial-gradient(circle, rgba(99, 102, 241, 0.08) 0%, transparent 70%)"
|
||||||
speed={speed}
|
pointerEvents="none"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 副标题 */}
|
<Container maxW="7xl" position="relative" zIndex={10} px={{ base: 4, md: 6 }}>
|
||||||
<Skeleton
|
<VStack
|
||||||
height={{ base: '20px', md: '24px' }}
|
spacing={{ base: 8, md: 12, lg: 16 }}
|
||||||
width={{ base: '90%', md: '600px' }}
|
align="stretch"
|
||||||
borderRadius="md"
|
minH="100vh"
|
||||||
speed={speed}
|
justify="center"
|
||||||
/>
|
>
|
||||||
|
{/* Hero 区域骨架 */}
|
||||||
|
<HeroSkeleton speed={speed} />
|
||||||
|
|
||||||
{/* CTA 按钮 */}
|
{/* 功能卡片区域骨架 */}
|
||||||
<HStack spacing={4} mt={4}>
|
<Box pb={{ base: 8, md: 12 }}>
|
||||||
<Skeleton
|
<VStack spacing={{ base: 6, md: 8 }}>
|
||||||
height="48px"
|
{/* 特色功能卡片骨架 */}
|
||||||
width="140px"
|
<Box w="100%">
|
||||||
borderRadius="lg"
|
<FeatureCardSkeleton isFeatured speed={speed} />
|
||||||
speed={speed}
|
</Box>
|
||||||
/>
|
|
||||||
<Skeleton
|
|
||||||
height="48px"
|
|
||||||
width="140px"
|
|
||||||
borderRadius="lg"
|
|
||||||
speed={speed}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
|
|
||||||
{/* 功能卡片网格骨架 */}
|
{/* 其他功能卡片骨架 */}
|
||||||
<SimpleGrid
|
<SimpleGrid
|
||||||
columns={{ base: 1, md: 2, lg: 3 }}
|
columns={{ base: 1, md: 2, lg: 3 }}
|
||||||
spacing={{ base: 6, md: 8 }}
|
spacing={{ base: 4, md: 5, lg: 6 }}
|
||||||
mt={8}
|
w="100%"
|
||||||
>
|
>
|
||||||
{/* 第一张卡片 - Featured (新闻中心) */}
|
{[1, 2, 3, 4, 5].map((index) => (
|
||||||
<Box gridColumn={{ base: 'span 1', lg: 'span 2' }}>
|
<FeatureCardSkeleton key={index} speed={speed} />
|
||||||
<FeatureCardSkeleton isFeatured />
|
))}
|
||||||
</Box>
|
</SimpleGrid>
|
||||||
|
</VStack>
|
||||||
{/* 其余 5 张卡片 */}
|
</Box>
|
||||||
{[1, 2, 3, 4, 5].map((index) => (
|
|
||||||
<FeatureCardSkeleton key={index} />
|
|
||||||
))}
|
|
||||||
</SimpleGrid>
|
|
||||||
|
|
||||||
{/* 底部装饰元素骨架 */}
|
|
||||||
<HStack justify="center" spacing={8} mt={12}>
|
|
||||||
{[1, 2, 3].map((index) => (
|
|
||||||
<VStack key={index} spacing={2} align="center">
|
|
||||||
<Skeleton
|
|
||||||
height="40px"
|
|
||||||
width="40px"
|
|
||||||
borderRadius="full"
|
|
||||||
speed={speed}
|
|
||||||
/>
|
|
||||||
<Skeleton height="16px" width="60px" borderRadius="md" speed={speed} />
|
|
||||||
</VStack>
|
|
||||||
))}
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
26
src/views/Home/index.tsx
Normal file
26
src/views/Home/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 首页入口组件
|
||||||
|
* 使用专用骨架屏作为 Suspense fallback,优化加载体验
|
||||||
|
*
|
||||||
|
* @module views/Home
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Suspense, lazy } from 'react';
|
||||||
|
import { HomePageSkeleton } from './components/HomePageSkeleton';
|
||||||
|
|
||||||
|
// 懒加载实际首页组件
|
||||||
|
const HomePage = lazy(() => import('./HomePage'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首页入口 - 带骨架屏的懒加载包装
|
||||||
|
* 使用深色风格骨架屏,与首页视觉风格一致
|
||||||
|
*/
|
||||||
|
const HomePageEntry: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<HomePageSkeleton />}>
|
||||||
|
<HomePage />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomePageEntry;
|
||||||
Reference in New Issue
Block a user