perf: EventPanel 性能优化,EventDetailCard H5适配,清理冗余类型
This commit is contained in:
@@ -84,15 +84,15 @@ export const EventDetailCard: React.FC<EventDetailCardProps> = ({
|
||||
|
||||
return (
|
||||
<Box
|
||||
p={4}
|
||||
p={{ base: 3, md: 4 }}
|
||||
borderRadius="md"
|
||||
border="1px"
|
||||
borderColor={borderColor}
|
||||
>
|
||||
{/* 标题和标签 */}
|
||||
<Flex justify="space-between" align="start" mb={2}>
|
||||
<HStack flexWrap="wrap" flex={1}>
|
||||
<Text fontWeight="bold" fontSize="lg">
|
||||
<Flex justify="space-between" align="start" mb={{ base: 1, md: 2 }} gap={{ base: 1, md: 2 }}>
|
||||
<HStack flexWrap="wrap" flex={1} spacing={{ base: 1, md: 2 }} gap={1}>
|
||||
<Text fontWeight="bold" fontSize={{ base: 'md', md: 'lg' }}>
|
||||
{event.title}
|
||||
</Text>
|
||||
{getEventBadge()}
|
||||
@@ -101,10 +101,10 @@ export const EventDetailCard: React.FC<EventDetailCardProps> = ({
|
||||
|
||||
{/* 描述内容 - 支持展开/收起 */}
|
||||
{event.description && (
|
||||
<Box mb={2}>
|
||||
<Box mb={{ base: 1, md: 2 }}>
|
||||
<Text
|
||||
ref={descriptionRef}
|
||||
fontSize="sm"
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
color={secondaryText}
|
||||
noOfLines={isExpanded ? undefined : MAX_LINES}
|
||||
whiteSpace="pre-wrap"
|
||||
@@ -128,8 +128,8 @@ export const EventDetailCard: React.FC<EventDetailCardProps> = ({
|
||||
|
||||
{/* 相关股票 */}
|
||||
{event.stocks && event.stocks.length > 0 && (
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
<Text fontSize="sm" color={secondaryText}>相关股票:</Text>
|
||||
<HStack spacing={{ base: 1, md: 2 }} flexWrap="wrap" gap={1}>
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} color={secondaryText}>相关股票:</Text>
|
||||
{event.stocks.map((stock, i) => (
|
||||
<Tag key={i} size="sm" colorScheme="blue" mb={1}>
|
||||
<TagLeftIcon as={FiTrendingUp} />
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* - label: 显示文案
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import React, { useState, useEffect, useRef, memo, useCallback } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Badge,
|
||||
@@ -58,6 +58,135 @@ interface StatusInfo {
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态信息
|
||||
*/
|
||||
const getStatusInfo = (status?: EventStatus): StatusInfo => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return { icon: FiCheckCircle, color: 'green', text: '已完成' };
|
||||
case 'cancelled':
|
||||
return { icon: FiXCircle, color: 'red', text: '已取消' };
|
||||
default:
|
||||
return { icon: FiAlertCircle, color: 'blue', text: '进行中' };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* EventCard Props
|
||||
*/
|
||||
interface EventCardProps {
|
||||
item: InvestmentEvent;
|
||||
colorScheme: string;
|
||||
label: string;
|
||||
textColor: string;
|
||||
secondaryText: string;
|
||||
cardBg: string;
|
||||
onEdit: (item: InvestmentEvent) => void;
|
||||
onDelete: (id: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* EventCard 组件(使用 memo 优化渲染性能)
|
||||
*/
|
||||
const EventCard = memo<EventCardProps>(({
|
||||
item,
|
||||
colorScheme,
|
||||
label,
|
||||
textColor,
|
||||
secondaryText,
|
||||
cardBg,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}) => {
|
||||
const statusInfo = getStatusInfo(item.status);
|
||||
|
||||
return (
|
||||
<Card
|
||||
bg={cardBg}
|
||||
shadow="sm"
|
||||
_hover={{ shadow: 'md' }}
|
||||
transition="all 0.2s"
|
||||
>
|
||||
<CardBody p={{ base: 2, md: 3 }}>
|
||||
<VStack align="stretch" spacing={{ base: 2, md: 3 }}>
|
||||
<Flex justify="space-between" align="start">
|
||||
<VStack align="start" spacing={1} flex={1}>
|
||||
<HStack spacing={{ base: 1, md: 2 }}>
|
||||
<Icon as={FiFileText} color={`${colorScheme}.500`} boxSize={{ base: 4, md: 5 }} />
|
||||
<Text fontWeight="bold" fontSize={{ base: 'md', md: 'lg' }}>
|
||||
{item.title}
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack spacing={{ base: 1, md: 2 }}>
|
||||
<Icon as={FiCalendar} boxSize={{ base: 2.5, md: 3 }} color={secondaryText} />
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} color={secondaryText}>
|
||||
{dayjs(item.event_date || item.date).format('YYYY年MM月DD日')}
|
||||
</Text>
|
||||
<Badge
|
||||
colorScheme={statusInfo.color}
|
||||
variant="subtle"
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
>
|
||||
{statusInfo.text}
|
||||
</Badge>
|
||||
</HStack>
|
||||
</VStack>
|
||||
<HStack spacing={{ base: 0, md: 1 }}>
|
||||
<IconButton
|
||||
icon={<FiEdit2 />}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => onEdit(item)}
|
||||
aria-label={`编辑${label}`}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<FiTrash2 />}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
colorScheme="red"
|
||||
onClick={() => onDelete(item.id)}
|
||||
aria-label={`删除${label}`}
|
||||
/>
|
||||
</HStack>
|
||||
</Flex>
|
||||
|
||||
{(item.content || item.description) && (
|
||||
<Text fontSize={{ base: 'xs', md: 'sm' }} color={textColor} noOfLines={3}>
|
||||
{item.content || item.description}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<HStack spacing={{ base: 1, md: 2 }} flexWrap="wrap" gap={1}>
|
||||
{item.stocks && item.stocks.length > 0 && (
|
||||
<>
|
||||
{item.stocks.map((stock, idx) => (
|
||||
<Tag key={idx} size="sm" colorScheme="blue" variant="subtle">
|
||||
<TagLeftIcon as={FiTrendingUp} />
|
||||
<TagLabel>{stock}</TagLabel>
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{item.tags && item.tags.length > 0 && (
|
||||
<>
|
||||
{item.tags.map((tag, idx) => (
|
||||
<Tag key={idx} size="sm" colorScheme={colorScheme} variant="subtle">
|
||||
<TagLeftIcon as={FiHash} />
|
||||
<TagLabel>{tag}</TagLabel>
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
EventCard.displayName = 'EventCard';
|
||||
|
||||
/**
|
||||
* EventPanel Props
|
||||
*/
|
||||
@@ -161,105 +290,10 @@ export const EventPanel: React.FC<EventPanelProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态信息
|
||||
const getStatusInfo = (status?: EventStatus): StatusInfo => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return { icon: FiCheckCircle, color: 'green', text: '已完成' };
|
||||
case 'cancelled':
|
||||
return { icon: FiXCircle, color: 'red', text: '已取消' };
|
||||
default:
|
||||
return { icon: FiAlertCircle, color: 'blue', text: '进行中' };
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染单个卡片
|
||||
const renderCard = (item: InvestmentEvent): React.ReactElement => {
|
||||
const statusInfo = getStatusInfo(item.status);
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={item.id}
|
||||
bg={cardBg}
|
||||
shadow="sm"
|
||||
_hover={{ shadow: 'md' }}
|
||||
transition="all 0.2s"
|
||||
>
|
||||
<CardBody>
|
||||
<VStack align="stretch" spacing={3}>
|
||||
<Flex justify="space-between" align="start">
|
||||
<VStack align="start" spacing={1} flex={1}>
|
||||
<HStack>
|
||||
<Icon as={FiFileText} color={`${colorScheme}.500`} />
|
||||
<Text fontWeight="bold" fontSize="lg">
|
||||
{item.title}
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiCalendar} boxSize={3} color={secondaryText} />
|
||||
<Text fontSize="sm" color={secondaryText}>
|
||||
{dayjs(item.event_date || item.date).format('YYYY年MM月DD日')}
|
||||
</Text>
|
||||
<Badge
|
||||
colorScheme={statusInfo.color}
|
||||
variant="subtle"
|
||||
>
|
||||
{statusInfo.text}
|
||||
</Badge>
|
||||
</HStack>
|
||||
</VStack>
|
||||
<HStack>
|
||||
<IconButton
|
||||
icon={<FiEdit2 />}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleOpenModal(item)}
|
||||
aria-label={`编辑${label}`}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<FiTrash2 />}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
colorScheme="red"
|
||||
onClick={() => handleDelete(item.id)}
|
||||
aria-label={`删除${label}`}
|
||||
/>
|
||||
</HStack>
|
||||
</Flex>
|
||||
|
||||
{(item.content || item.description) && (
|
||||
<Text fontSize="sm" color={textColor} noOfLines={3}>
|
||||
{item.content || item.description}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
{item.stocks && item.stocks.length > 0 && (
|
||||
<>
|
||||
{item.stocks.map((stock, idx) => (
|
||||
<Tag key={idx} size="sm" colorScheme="blue" variant="subtle">
|
||||
<TagLeftIcon as={FiTrendingUp} />
|
||||
<TagLabel>{stock}</TagLabel>
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{item.tags && item.tags.length > 0 && (
|
||||
<>
|
||||
{item.tags.map((tag, idx) => (
|
||||
<Tag key={idx} size="sm" colorScheme={colorScheme} variant="subtle">
|
||||
<TagLeftIcon as={FiHash} />
|
||||
<TagLabel>{tag}</TagLabel>
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
// 使用 useCallback 优化回调函数
|
||||
const handleEdit = useCallback((item: InvestmentEvent) => {
|
||||
handleOpenModal(item);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@@ -276,8 +310,20 @@ export const EventPanel: React.FC<EventPanelProps> = ({
|
||||
</VStack>
|
||||
</Center>
|
||||
) : (
|
||||
<Grid templateColumns={{ base: '1fr', md: 'repeat(2, 1fr)' }} gap={4}>
|
||||
{events.map(renderCard)}
|
||||
<Grid templateColumns={{ base: '1fr', md: 'repeat(2, 1fr)' }} gap={{ base: 3, md: 4 }}>
|
||||
{events.map(event => (
|
||||
<EventCard
|
||||
key={event.id}
|
||||
item={event}
|
||||
colorScheme={colorScheme}
|
||||
label={label}
|
||||
textColor={textColor}
|
||||
secondaryText={secondaryText}
|
||||
cardBg={cardBg}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</VStack>
|
||||
|
||||
@@ -129,7 +129,6 @@ const InvestmentPlanningCenter: React.FC = () => {
|
||||
openPlanModalTrigger,
|
||||
openReviewModalTrigger,
|
||||
toast,
|
||||
bgColor,
|
||||
borderColor,
|
||||
textColor,
|
||||
secondaryText,
|
||||
|
||||
Reference in New Issue
Block a user