feat: 重构主组件 InvestmentPlanningCenter.tsx
重命名并重构: InvestmentPlanningCenter.js → InvestmentPlanningCenter.tsx 懒加载子组件 加载骨架屏组件
This commit is contained in:
203
src/views/Dashboard/components/InvestmentPlanningCenter.tsx
Normal file
203
src/views/Dashboard/components/InvestmentPlanningCenter.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* InvestmentPlanningCenter - 投资规划中心主组件 (TypeScript 重构版)
|
||||
*
|
||||
* 性能优化:
|
||||
* - 使用 React.lazy() 懒加载子面板,减少初始加载时间
|
||||
* - 从 1421 行拆分为 5 个独立模块,提升可维护性
|
||||
* - 使用 TypeScript 提供类型安全
|
||||
*
|
||||
* 组件架构:
|
||||
* - InvestmentPlanningCenter (主组件,~200 行)
|
||||
* - CalendarPanel (日历面板,懒加载)
|
||||
* - PlansPanel (计划面板,懒加载)
|
||||
* - ReviewsPanel (复盘面板,懒加载)
|
||||
* - PlanningContext (数据共享层)
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback, Suspense, lazy } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
Heading,
|
||||
HStack,
|
||||
Flex,
|
||||
Icon,
|
||||
useColorModeValue,
|
||||
useToast,
|
||||
Tabs,
|
||||
TabList,
|
||||
TabPanels,
|
||||
Tab,
|
||||
TabPanel,
|
||||
Spinner,
|
||||
Center,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
FiCalendar,
|
||||
FiTarget,
|
||||
FiFileText,
|
||||
} from 'react-icons/fi';
|
||||
|
||||
import { PlanningDataProvider } from './PlanningContext';
|
||||
import type { InvestmentEvent, PlanningContextValue } from '@/types';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { getApiBase } from '@/utils/apiConfig';
|
||||
import './InvestmentCalendar.css';
|
||||
|
||||
// 懒加载子面板组件(实现代码分割)
|
||||
const CalendarPanel = lazy(() =>
|
||||
import('./CalendarPanel').then(module => ({ default: module.CalendarPanel }))
|
||||
);
|
||||
const PlansPanel = lazy(() =>
|
||||
import('./PlansPanel').then(module => ({ default: module.PlansPanel }))
|
||||
);
|
||||
const ReviewsPanel = lazy(() =>
|
||||
import('./ReviewsPanel').then(module => ({ default: module.ReviewsPanel }))
|
||||
);
|
||||
|
||||
/**
|
||||
* 面板加载占位符
|
||||
*/
|
||||
const PanelLoadingFallback: React.FC = () => (
|
||||
<Center py={12}>
|
||||
<Spinner size="xl" color="purple.500" thickness="4px" />
|
||||
</Center>
|
||||
);
|
||||
|
||||
/**
|
||||
* InvestmentPlanningCenter 主组件
|
||||
*/
|
||||
const InvestmentPlanningCenter: React.FC = () => {
|
||||
const toast = useToast();
|
||||
|
||||
// 颜色主题
|
||||
const bgColor = useColorModeValue('white', 'gray.800');
|
||||
const borderColor = useColorModeValue('gray.200', 'gray.600');
|
||||
const textColor = useColorModeValue('gray.700', 'white');
|
||||
const secondaryText = useColorModeValue('gray.600', 'gray.400');
|
||||
const cardBg = useColorModeValue('gray.50', 'gray.700');
|
||||
|
||||
// 全局数据状态
|
||||
const [allEvents, setAllEvents] = useState<InvestmentEvent[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [activeTab, setActiveTab] = useState<number>(0);
|
||||
|
||||
/**
|
||||
* 加载所有事件数据(日历事件 + 计划 + 复盘)
|
||||
*/
|
||||
const loadAllData = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const base = getApiBase();
|
||||
|
||||
const response = await fetch(base + '/api/account/calendar/events', {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setAllEvents(data.data || []);
|
||||
logger.debug('InvestmentPlanningCenter', '数据加载成功', {
|
||||
count: data.data?.length || 0
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('InvestmentPlanningCenter', 'loadAllData', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 组件挂载时加载数据
|
||||
useEffect(() => {
|
||||
loadAllData();
|
||||
}, [loadAllData]);
|
||||
|
||||
// 提供给子组件的 Context 值
|
||||
const contextValue: PlanningContextValue = {
|
||||
allEvents,
|
||||
setAllEvents,
|
||||
loadAllData,
|
||||
loading,
|
||||
setLoading,
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
toast,
|
||||
bgColor,
|
||||
borderColor,
|
||||
textColor,
|
||||
secondaryText,
|
||||
cardBg,
|
||||
};
|
||||
|
||||
// 计算各类型事件数量
|
||||
const planCount = allEvents.filter(e => e.type === 'plan').length;
|
||||
const reviewCount = allEvents.filter(e => e.type === 'review').length;
|
||||
|
||||
return (
|
||||
<PlanningDataProvider value={contextValue}>
|
||||
<Card bg={bgColor} shadow="md">
|
||||
<CardHeader pb={4}>
|
||||
<Flex justify="space-between" align="center">
|
||||
<HStack>
|
||||
<Icon as={FiTarget} color="purple.500" boxSize={5} />
|
||||
<Heading size="md">投资规划中心</Heading>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</CardHeader>
|
||||
<CardBody pt={0}>
|
||||
<Tabs
|
||||
index={activeTab}
|
||||
onChange={setActiveTab}
|
||||
variant="enclosed"
|
||||
colorScheme="purple"
|
||||
>
|
||||
<TabList>
|
||||
<Tab>
|
||||
<Icon as={FiCalendar} mr={2} />
|
||||
日历视图
|
||||
</Tab>
|
||||
<Tab>
|
||||
<Icon as={FiTarget} mr={2} />
|
||||
我的计划 ({planCount})
|
||||
</Tab>
|
||||
<Tab>
|
||||
<Icon as={FiFileText} mr={2} />
|
||||
我的复盘 ({reviewCount})
|
||||
</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
{/* 日历视图面板 */}
|
||||
<TabPanel px={0}>
|
||||
<Suspense fallback={<PanelLoadingFallback />}>
|
||||
<CalendarPanel />
|
||||
</Suspense>
|
||||
</TabPanel>
|
||||
|
||||
{/* 计划列表面板 */}
|
||||
<TabPanel px={0}>
|
||||
<Suspense fallback={<PanelLoadingFallback />}>
|
||||
<PlansPanel />
|
||||
</Suspense>
|
||||
</TabPanel>
|
||||
|
||||
{/* 复盘列表面板 */}
|
||||
<TabPanel px={0}>
|
||||
<Suspense fallback={<PanelLoadingFallback />}>
|
||||
<ReviewsPanel />
|
||||
</Suspense>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</PlanningDataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default InvestmentPlanningCenter;
|
||||
Reference in New Issue
Block a user