refactor(Center): 全面优化个人中心模块

- 目录重命名:Dashboard → Center(匹配路由 /home/center)
- 删除遗留代码:Default.js、InvestmentPlansAndReviews.js、InvestmentCalendarChakra.js(共 2596 行)
- 创建 src/types/center.ts 类型定义(15+ 接口)
- 性能优化:
  - 创建 useCenterColors Hook 封装 7 个 useColorModeValue
  - 创建 utils/formatters.ts 提取纯函数
  - 修复 loadRealtimeQuotes 的 useCallback 依赖项
  - InvestmentPlanningCenter 添加 useMemo 缓存
- TypeScript 迁移:Center.js → Center.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-22 18:57:28 +08:00
parent c639b418f0
commit 18ba36a539
23 changed files with 658 additions and 2778 deletions

View File

@@ -0,0 +1,187 @@
/**
* EventPanel - 通用事件面板组件
* 用于显示、编辑和管理投资计划或复盘
*
* 通过 props 配置差异化行为:
* - type: 'plan' | 'review'
* - colorScheme: 主题色
* - label: 显示文案
*/
import React, { useState, useEffect, useRef, useCallback } from 'react';
import {
Box,
Grid,
VStack,
Text,
Spinner,
Center,
Icon,
} from '@chakra-ui/react';
import { FiFileText } from 'react-icons/fi';
import { usePlanningData } from './PlanningContext';
import { EventFormModal } from './EventFormModal';
import { EventCard } from './EventCard';
import type { InvestmentEvent } from '@/types';
import { logger } from '@/utils/logger';
import { getApiBase } from '@/utils/apiConfig';
/**
* EventPanel Props
*/
export interface EventPanelProps {
/** 事件类型 */
type: 'plan' | 'review';
/** 主题颜色 */
colorScheme: string;
/** 显示标签(如 "计划" 或 "复盘" */
label: string;
/** 外部触发打开模态框的计数器 */
openModalTrigger?: number;
}
/**
* EventPanel 组件
* 通用事件列表面板,显示投资计划或复盘
*/
export const EventPanel: React.FC<EventPanelProps> = ({
type,
colorScheme,
label,
openModalTrigger,
}) => {
const {
allEvents,
loadAllData,
loading,
toast,
textColor,
secondaryText,
cardBg,
} = usePlanningData();
// 弹窗状态
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [editingItem, setEditingItem] = useState<InvestmentEvent | null>(null);
const [modalMode, setModalMode] = useState<'create' | 'edit'>('create');
// 使用 ref 记录上一次的 trigger 值,避免组件挂载时误触发
const prevTriggerRef = useRef<number>(openModalTrigger || 0);
// 筛选事件列表(按类型过滤,排除系统事件)
const events = allEvents.filter(event => event.type === type && event.source !== 'future');
// 监听外部触发打开新建模态框(修复 bug只在值变化时触发
useEffect(() => {
if (openModalTrigger !== undefined && openModalTrigger > prevTriggerRef.current) {
// 只有当 trigger 值增加时才打开弹窗
handleOpenModal(null);
}
prevTriggerRef.current = openModalTrigger || 0;
}, [openModalTrigger]);
// 打开编辑/新建模态框
const handleOpenModal = (item: InvestmentEvent | null = null): void => {
if (item) {
setEditingItem(item);
setModalMode('edit');
} else {
setEditingItem(null);
setModalMode('create');
}
setIsModalOpen(true);
};
// 关闭弹窗
const handleCloseModal = (): void => {
setIsModalOpen(false);
setEditingItem(null);
};
// 删除数据
const handleDelete = async (id: number): Promise<void> => {
if (!window.confirm('确定要删除吗?')) return;
try {
const base = getApiBase();
const response = await fetch(base + `/api/account/investment-plans/${id}`, {
method: 'DELETE',
credentials: 'include',
});
if (response.ok) {
logger.info('EventPanel', `删除${label}成功`, { itemId: id });
toast({
title: '删除成功',
status: 'success',
duration: 2000,
});
loadAllData();
}
} catch (error) {
logger.error('EventPanel', 'handleDelete', error, { itemId: id });
toast({
title: '删除失败',
status: 'error',
duration: 3000,
});
}
};
// 使用 useCallback 优化回调函数
const handleEdit = useCallback((item: InvestmentEvent) => {
handleOpenModal(item);
}, []);
return (
<Box>
<VStack align="stretch" spacing={4}>
{loading ? (
<Center py={8}>
<Spinner size="xl" color={`${colorScheme}.500`} />
</Center>
) : events.length === 0 ? (
<Center py={{ base: 6, md: 8 }}>
<VStack spacing={{ base: 2, md: 3 }}>
<Icon as={FiFileText} boxSize={{ base: 8, md: 12 }} color="gray.300" />
<Text color={secondaryText} fontSize={{ base: 'sm', md: 'md' }}>{label}</Text>
</VStack>
</Center>
) : (
<Grid templateColumns={{ base: '1fr', md: 'repeat(2, 1fr)' }} gap={{ base: 3, md: 4 }}>
{events.map(event => (
<EventCard
key={event.id}
event={event}
variant="list"
colorScheme={colorScheme}
label={label}
textColor={textColor}
secondaryText={secondaryText}
cardBg={cardBg}
onEdit={handleEdit}
onDelete={handleDelete}
/>
))}
</Grid>
)}
</VStack>
{/* 使用通用弹窗组件 */}
<EventFormModal
isOpen={isModalOpen}
onClose={handleCloseModal}
mode={modalMode}
eventType={type}
editingEvent={editingItem}
onSuccess={loadAllData}
label={label}
apiEndpoint="investment-plans"
/>
</Box>
);
};
export default EventPanel;