:pref: 首屏优化
This commit is contained in:
@@ -53,10 +53,15 @@ function AppContent() {
|
|||||||
const pageEnterTimeRef = useRef(Date.now());
|
const pageEnterTimeRef = useRef(Date.now());
|
||||||
const currentPathRef = useRef(location.pathname);
|
const currentPathRef = useRef(location.pathname);
|
||||||
|
|
||||||
// 🎯 PostHog Redux 初始化
|
// 🎯 PostHog Redux 初始化(延迟执行,不阻塞首屏)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// ⚡ 延迟 2 秒初始化 PostHog,确保首屏渲染不被阻塞
|
||||||
|
const timer = setTimeout(() => {
|
||||||
dispatch(initializePostHog());
|
dispatch(initializePostHog());
|
||||||
logger.info('App', 'PostHog Redux 初始化已触发');
|
logger.info('App', 'PostHog Redux 初始化已触发(延迟 2 秒)');
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
// ⚡ 性能监控:标记 React 初始化完成
|
// ⚡ 性能监控:标记 React 初始化完成
|
||||||
|
|||||||
38
src/index.js
38
src/index.js
@@ -119,19 +119,13 @@ function registerServiceWorker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动 Mock Service Worker(如果启用)
|
// 启动应用(MSW 异步初始化,不阻塞首屏渲染)
|
||||||
async function startApp() {
|
function startApp() {
|
||||||
// 只在开发环境启动 MSW
|
|
||||||
if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_ENABLE_MOCK === 'true') {
|
|
||||||
const { startMockServiceWorker } = await import('./mocks/browser');
|
|
||||||
await startMockServiceWorker();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create root
|
// Create root
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
|
||||||
// Render the app with Router wrapper
|
// ✅ 先渲染应用,不等待 MSW
|
||||||
// ✅ StrictMode 已启用(Chakra UI 2.10.9+ 已修复兼容性问题)
|
// StrictMode 已启用(Chakra UI 2.10.9+ 已修复兼容性问题)
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Router
|
<Router
|
||||||
@@ -145,6 +139,30 @@ async function startApp() {
|
|||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ✅ 后台异步启动 MSW(不阻塞首屏渲染)
|
||||||
|
if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_ENABLE_MOCK === 'true') {
|
||||||
|
const initMSW = async () => {
|
||||||
|
try {
|
||||||
|
const { startMockServiceWorker } = await import('./mocks/browser');
|
||||||
|
await startMockServiceWorker();
|
||||||
|
console.log(
|
||||||
|
'%c[MSW] ✅ Mock Service Worker 已在后台启动',
|
||||||
|
'color: #4CAF50; font-weight: bold;'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[MSW] ❌ Mock Service Worker 启动失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用 requestIdleCallback 在浏览器空闲时初始化,不阻塞首屏
|
||||||
|
if ('requestIdleCallback' in window) {
|
||||||
|
requestIdleCallback(() => initMSW(), { timeout: 3000 });
|
||||||
|
} else {
|
||||||
|
// 降级:使用 setTimeout(0) 延迟执行
|
||||||
|
setTimeout(initMSW, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 注册 Service Worker
|
// 注册 Service Worker
|
||||||
registerServiceWorker();
|
registerServiceWorker();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
} from '../data/users';
|
} from '../data/users';
|
||||||
|
|
||||||
// 模拟网络延迟(毫秒)
|
// 模拟网络延迟(毫秒)
|
||||||
const NETWORK_DELAY = 500;
|
// ⚡ 开发环境使用较短延迟,加快首屏加载速度
|
||||||
|
const NETWORK_DELAY = 50;
|
||||||
|
|
||||||
export const authHandlers = [
|
export const authHandlers = [
|
||||||
// ==================== 手机验证码登录 ====================
|
// ==================== 手机验证码登录 ====================
|
||||||
@@ -356,7 +357,7 @@ export const authHandlers = [
|
|||||||
|
|
||||||
// 7. 检查 Session(AuthContext 使用的正确端点)
|
// 7. 检查 Session(AuthContext 使用的正确端点)
|
||||||
http.get('/api/auth/session', async () => {
|
http.get('/api/auth/session', async () => {
|
||||||
await delay(300);
|
await delay(NETWORK_DELAY); // ⚡ 使用统一延迟配置
|
||||||
|
|
||||||
// 获取当前登录用户
|
// 获取当前登录用户
|
||||||
const currentUser = getCurrentUser();
|
const currentUser = getCurrentUser();
|
||||||
@@ -380,7 +381,7 @@ export const authHandlers = [
|
|||||||
|
|
||||||
// 8. 检查 Session(旧端点,保留兼容)
|
// 8. 检查 Session(旧端点,保留兼容)
|
||||||
http.get('/api/auth/check-session', async () => {
|
http.get('/api/auth/check-session', async () => {
|
||||||
await delay(300);
|
await delay(NETWORK_DELAY); // ⚡ 使用统一延迟配置
|
||||||
|
|
||||||
// 获取当前登录用户
|
// 获取当前登录用户
|
||||||
const currentUser = getCurrentUser();
|
const currentUser = getCurrentUser();
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import React from 'react';
|
|||||||
*/
|
*/
|
||||||
export const lazyComponents = {
|
export const lazyComponents = {
|
||||||
// Home 模块
|
// Home 模块
|
||||||
// 首页使用专用入口组件,内置骨架屏 fallback
|
// ⚡ 直接引用 HomePage,无需中间层(静态页面不需要骨架屏)
|
||||||
HomePage: React.lazy(() => import('@views/Home')),
|
HomePage: React.lazy(() => import('@views/Home/HomePage')),
|
||||||
CenterDashboard: React.lazy(() => import('@views/Dashboard/Center')),
|
CenterDashboard: React.lazy(() => import('@views/Dashboard/Center')),
|
||||||
ProfilePage: React.lazy(() => import('@views/Profile')),
|
ProfilePage: React.lazy(() => import('@views/Profile')),
|
||||||
SettingsPage: React.lazy(() => import('@views/Settings/SettingsPage')),
|
SettingsPage: React.lazy(() => import('@views/Settings/SettingsPage')),
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
/**
|
|
||||||
* 首页骨架屏组件
|
|
||||||
* 匹配首页深色科技风格,减少白屏感知时间
|
|
||||||
*
|
|
||||||
* @module views/Home/components/HomePageSkeleton
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Container,
|
|
||||||
SimpleGrid,
|
|
||||||
Skeleton,
|
|
||||||
SkeletonText,
|
|
||||||
VStack,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 类型定义
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
interface HomePageSkeletonProps {
|
|
||||||
/** 骨架屏速度(秒) */
|
|
||||||
speed?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 单个卡片骨架 - 深色风格
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
const FeatureCardSkeleton: React.FC<{ isFeatured?: boolean; speed?: number }> = ({
|
|
||||||
isFeatured = false,
|
|
||||||
speed = 0.8,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
bg="rgba(255, 255, 255, 0.03)"
|
|
||||||
borderRadius="2xl"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor="whiteAlpha.100"
|
|
||||||
p={{ base: 5, md: isFeatured ? 8 : 6 }}
|
|
||||||
h={isFeatured ? { base: '280px', md: '320px' } : { base: '200px', md: '240px' }}
|
|
||||||
backdropFilter="blur(10px)"
|
|
||||||
>
|
|
||||||
<VStack align="start" spacing={4} h="full">
|
|
||||||
{/* 图标骨架 */}
|
|
||||||
<Skeleton
|
|
||||||
height={isFeatured ? '56px' : '44px'}
|
|
||||||
width={isFeatured ? '56px' : '44px'}
|
|
||||||
borderRadius="xl"
|
|
||||||
startColor="whiteAlpha.100"
|
|
||||||
endColor="whiteAlpha.200"
|
|
||||||
speed={speed}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 标题骨架 */}
|
|
||||||
<Skeleton
|
|
||||||
height="24px"
|
|
||||||
width="60%"
|
|
||||||
borderRadius="md"
|
|
||||||
startColor="whiteAlpha.100"
|
|
||||||
endColor="whiteAlpha.200"
|
|
||||||
speed={speed}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 描述骨架 */}
|
|
||||||
<SkeletonText
|
|
||||||
mt="2"
|
|
||||||
noOfLines={isFeatured ? 3 : 2}
|
|
||||||
spacing="3"
|
|
||||||
skeletonHeight="3"
|
|
||||||
width="90%"
|
|
||||||
startColor="whiteAlpha.50"
|
|
||||||
endColor="whiteAlpha.100"
|
|
||||||
speed={speed}
|
|
||||||
/>
|
|
||||||
</VStack>
|
|
||||||
</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> = ({
|
|
||||||
speed = 0.8,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
w="full"
|
|
||||||
minH="100vh"
|
|
||||||
bg="linear-gradient(135deg, #0E0C15 0%, #15131D 50%, #252134 100%)"
|
|
||||||
position="relative"
|
|
||||||
overflow="hidden"
|
|
||||||
>
|
|
||||||
{/* 背景装饰 - 模拟光晕效果 */}
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
top="-20%"
|
|
||||||
left="50%"
|
|
||||||
transform="translateX(-50%)"
|
|
||||||
w="800px"
|
|
||||||
h="800px"
|
|
||||||
bg="radial-gradient(circle, rgba(99, 102, 241, 0.08) 0%, transparent 70%)"
|
|
||||||
pointerEvents="none"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Container maxW="7xl" position="relative" zIndex={10} px={{ base: 4, md: 6 }}>
|
|
||||||
<VStack
|
|
||||||
spacing={{ base: 8, md: 12, lg: 16 }}
|
|
||||||
align="stretch"
|
|
||||||
minH="100vh"
|
|
||||||
justify="center"
|
|
||||||
>
|
|
||||||
{/* Hero 区域骨架 */}
|
|
||||||
<HeroSkeleton speed={speed} />
|
|
||||||
|
|
||||||
{/* 功能卡片区域骨架 */}
|
|
||||||
<Box pb={{ base: 8, md: 12 }}>
|
|
||||||
<VStack spacing={{ base: 6, md: 8 }}>
|
|
||||||
{/* 特色功能卡片骨架 */}
|
|
||||||
<Box w="100%">
|
|
||||||
<FeatureCardSkeleton isFeatured speed={speed} />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 其他功能卡片骨架 */}
|
|
||||||
<SimpleGrid
|
|
||||||
columns={{ base: 1, md: 2, lg: 3 }}
|
|
||||||
spacing={{ base: 4, md: 5, lg: 6 }}
|
|
||||||
w="100%"
|
|
||||||
>
|
|
||||||
{[1, 2, 3, 4, 5].map((index) => (
|
|
||||||
<FeatureCardSkeleton key={index} speed={speed} />
|
|
||||||
))}
|
|
||||||
</SimpleGrid>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
</VStack>
|
|
||||||
</Container>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 默认导出
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
export default HomePageSkeleton;
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* 首页入口组件
|
|
||||||
* 使用专用骨架屏作为 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