perf(DynamicTracking): 优化组件加载体验,子组件懒加载

- 使用 React.lazy() 懒加载所有子面板组件
- 为每个 Tab 添加专属骨架屏 fallback
- SubTabContainer 同步渲染,点击立即显示二级导航
- 添加 memo、useCallback、useMemo 性能优化
- 新增 DynamicTrackingSkeleton.tsx 骨架屏组件

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-19 17:10:39 +08:00
parent 11544909d3
commit 8c9cc9845d
3 changed files with 170 additions and 17 deletions

View File

@@ -0,0 +1,101 @@
/**
* 动态跟踪 Tab 骨架屏组件
* 用于懒加载时显示,提供即时反馈
*/
import React from 'react';
import { Box, VStack, HStack, Skeleton, SkeletonText, Card, CardBody } from '@chakra-ui/react';
/**
* 新闻动态骨架屏
*/
export const NewsPanelSkeleton: React.FC = () => (
<VStack spacing={3} align="stretch">
{[1, 2, 3, 4, 5].map((i) => (
<Card key={i} bg="gray.900" border="1px solid" borderColor="rgba(212, 175, 55, 0.2)">
<CardBody py={3} px={4}>
<HStack spacing={3}>
<Skeleton height="40px" width="40px" borderRadius="md" />
<VStack align="start" flex={1} spacing={2}>
<Skeleton height="16px" width="80%" />
<Skeleton height="12px" width="40%" />
</VStack>
</HStack>
</CardBody>
</Card>
))}
</VStack>
);
/**
* 公告列表骨架屏
*/
export const AnnouncementsSkeleton: React.FC = () => (
<VStack spacing={3} align="stretch">
{[1, 2, 3, 4].map((i) => (
<Card key={i} bg="gray.900" border="1px solid" borderColor="rgba(212, 175, 55, 0.2)">
<CardBody py={3} px={4}>
<VStack align="start" spacing={2}>
<Skeleton height="16px" width="70%" />
<HStack spacing={4}>
<Skeleton height="12px" width="80px" />
<Skeleton height="12px" width="60px" />
</HStack>
</VStack>
</CardBody>
</Card>
))}
</VStack>
);
/**
* 财报披露日程骨架屏
*/
export const DisclosureScheduleSkeleton: React.FC = () => (
<Box>
<Skeleton height="200px" borderRadius="md" mb={4} />
<VStack spacing={2} align="stretch">
{[1, 2, 3].map((i) => (
<HStack key={i} justify="space-between" p={2}>
<Skeleton height="14px" width="100px" />
<Skeleton height="14px" width="60px" />
</HStack>
))}
</VStack>
</Box>
);
/**
* 业绩预告骨架屏
*/
export const ForecastPanelSkeleton: React.FC = () => (
<VStack spacing={4} align="stretch">
<Card bg="gray.900" border="1px solid" borderColor="rgba(212, 175, 55, 0.2)">
<CardBody>
<SkeletonText noOfLines={4} spacing={3} />
</CardBody>
</Card>
<Card bg="gray.900" border="1px solid" borderColor="rgba(212, 175, 55, 0.2)">
<CardBody>
<Skeleton height="120px" borderRadius="md" />
</CardBody>
</Card>
</VStack>
);
/**
* 通用内容骨架屏(默认 fallback
*/
export const ContentSkeleton: React.FC = () => (
<Box p={4}>
<SkeletonText noOfLines={6} spacing={4} />
</Box>
);
export default {
NewsPanelSkeleton,
AnnouncementsSkeleton,
DisclosureScheduleSkeleton,
ForecastPanelSkeleton,
ContentSkeleton,
};

View File

@@ -2,3 +2,12 @@
export { default as NewsPanel } from './NewsPanel'; export { default as NewsPanel } from './NewsPanel';
export { default as ForecastPanel } from './ForecastPanel'; export { default as ForecastPanel } from './ForecastPanel';
// 骨架屏组件
export {
NewsPanelSkeleton,
AnnouncementsSkeleton,
DisclosureScheduleSkeleton,
ForecastPanelSkeleton,
ContentSkeleton,
} from './DynamicTrackingSkeleton';

View File

@@ -1,37 +1,73 @@
// src/views/Company/components/DynamicTracking/index.js // src/views/Company/components/DynamicTracking/index.js
// 动态跟踪 - 独立一级 Tab 组件(包含新闻动态等二级 Tab // 动态跟踪 - 独立一级 Tab 组件(包含新闻动态等二级 Tab
// 优化:子组件懒加载,骨架屏即时反馈
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo, useCallback, memo, lazy } from 'react';
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { FaNewspaper, FaBullhorn, FaCalendarAlt, FaChartBar } from 'react-icons/fa'; import { FaNewspaper, FaBullhorn, FaCalendarAlt, FaChartBar } from 'react-icons/fa';
import SubTabContainer from '@components/SubTabContainer'; import SubTabContainer from '@components/SubTabContainer';
import AnnouncementsPanel from '../CompanyOverview/BasicInfoTab/components/AnnouncementsPanel'; import {
import DisclosureSchedulePanel from '../CompanyOverview/BasicInfoTab/components/DisclosureSchedulePanel'; NewsPanelSkeleton,
import { NewsPanel, ForecastPanel } from './components'; AnnouncementsSkeleton,
DisclosureScheduleSkeleton,
ForecastPanelSkeleton,
} from './components/DynamicTrackingSkeleton';
// 二级 Tab 配置 // 懒加载子组件
const NewsPanel = lazy(() => import('./components/NewsPanel'));
const ForecastPanel = lazy(() => import('./components/ForecastPanel'));
const AnnouncementsPanel = lazy(() =>
import('../CompanyOverview/BasicInfoTab/components/AnnouncementsPanel')
);
const DisclosureSchedulePanel = lazy(() =>
import('../CompanyOverview/BasicInfoTab/components/DisclosureSchedulePanel')
);
// 二级 Tab 配置(带骨架屏 fallback
const TRACKING_TABS = [ const TRACKING_TABS = [
{ key: 'news', name: '新闻动态', icon: FaNewspaper, component: NewsPanel }, {
{ key: 'announcements', name: '公司公告', icon: FaBullhorn, component: AnnouncementsPanel }, key: 'news',
{ key: 'disclosure', name: '财报披露日程', icon: FaCalendarAlt, component: DisclosureSchedulePanel }, name: '新闻动态',
{ key: 'forecast', name: '业绩预告', icon: FaChartBar, component: ForecastPanel }, icon: FaNewspaper,
component: NewsPanel,
fallback: <NewsPanelSkeleton />,
},
{
key: 'announcements',
name: '公司公告',
icon: FaBullhorn,
component: AnnouncementsPanel,
fallback: <AnnouncementsSkeleton />,
},
{
key: 'disclosure',
name: '财报披露日程',
icon: FaCalendarAlt,
component: DisclosureSchedulePanel,
fallback: <DisclosureScheduleSkeleton />,
},
{
key: 'forecast',
name: '业绩预告',
icon: FaChartBar,
component: ForecastPanel,
fallback: <ForecastPanelSkeleton />,
},
]; ];
/** /**
* 动态跟踪组件 * 动态跟踪组件
* *
* 功能: * 功能:
* - 使用 SubTabContainer 实现二级导航 * - 使用 SubTabContainer 实现二级导航(同步渲染,无 loading
* - Tab1: 新闻动态 * - 子组件懒加载,减少初始包体积
* - Tab2: 公司公告 * - 每个 Tab 有专属骨架屏,提供即时视觉反馈
* - Tab3: 财报披露日程
* - Tab4: 业绩预告
* *
* @param {Object} props * @param {Object} props
* @param {string} props.stockCode - 股票代码 * @param {string} props.stockCode - 股票代码
*/ */
const DynamicTracking = ({ stockCode: propStockCode }) => { const DynamicTracking = memo(({ stockCode: propStockCode }) => {
const [stockCode, setStockCode] = useState(propStockCode || '000001'); const [stockCode, setStockCode] = useState(propStockCode || '000001');
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
@@ -50,6 +86,11 @@ const DynamicTracking = ({ stockCode: propStockCode }) => {
[stockCode] [stockCode]
); );
// Tab 切换回调
const handleTabChange = useCallback((index) => {
setActiveTab(index);
}, []);
return ( return (
<Box> <Box>
<SubTabContainer <SubTabContainer
@@ -57,12 +98,14 @@ const DynamicTracking = ({ stockCode: propStockCode }) => {
componentProps={componentProps} componentProps={componentProps}
themePreset="blackGold" themePreset="blackGold"
index={activeTab} index={activeTab}
onTabChange={(index) => setActiveTab(index)} onTabChange={handleTabChange}
isLazy isLazy
size="sm" size="sm"
/> />
</Box> </Box>
); );
}; });
DynamicTracking.displayName = 'DynamicTracking';
export default DynamicTracking; export default DynamicTracking;