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:
187
src/views/Center/components/EventPanel.tsx
Normal file
187
src/views/Center/components/EventPanel.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user