perf: EventPanel 性能优化,EventDetailCard H5适配,清理冗余类型

This commit is contained in:
zdl
2025-12-05 15:03:56 +08:00
parent e283135ef8
commit df90fc258b
3 changed files with 156 additions and 111 deletions

View File

@@ -84,15 +84,15 @@ export const EventDetailCard: React.FC<EventDetailCardProps> = ({
return ( return (
<Box <Box
p={4} p={{ base: 3, md: 4 }}
borderRadius="md" borderRadius="md"
border="1px" border="1px"
borderColor={borderColor} borderColor={borderColor}
> >
{/* 标题和标签 */} {/* 标题和标签 */}
<Flex justify="space-between" align="start" mb={2}> <Flex justify="space-between" align="start" mb={{ base: 1, md: 2 }} gap={{ base: 1, md: 2 }}>
<HStack flexWrap="wrap" flex={1}> <HStack flexWrap="wrap" flex={1} spacing={{ base: 1, md: 2 }} gap={1}>
<Text fontWeight="bold" fontSize="lg"> <Text fontWeight="bold" fontSize={{ base: 'md', md: 'lg' }}>
{event.title} {event.title}
</Text> </Text>
{getEventBadge()} {getEventBadge()}
@@ -101,10 +101,10 @@ export const EventDetailCard: React.FC<EventDetailCardProps> = ({
{/* 描述内容 - 支持展开/收起 */} {/* 描述内容 - 支持展开/收起 */}
{event.description && ( {event.description && (
<Box mb={2}> <Box mb={{ base: 1, md: 2 }}>
<Text <Text
ref={descriptionRef} ref={descriptionRef}
fontSize="sm" fontSize={{ base: 'xs', md: 'sm' }}
color={secondaryText} color={secondaryText}
noOfLines={isExpanded ? undefined : MAX_LINES} noOfLines={isExpanded ? undefined : MAX_LINES}
whiteSpace="pre-wrap" whiteSpace="pre-wrap"
@@ -128,8 +128,8 @@ export const EventDetailCard: React.FC<EventDetailCardProps> = ({
{/* 相关股票 */} {/* 相关股票 */}
{event.stocks && event.stocks.length > 0 && ( {event.stocks && event.stocks.length > 0 && (
<HStack spacing={2} flexWrap="wrap"> <HStack spacing={{ base: 1, md: 2 }} flexWrap="wrap" gap={1}>
<Text fontSize="sm" color={secondaryText}>:</Text> <Text fontSize={{ base: 'xs', md: 'sm' }} color={secondaryText}>:</Text>
{event.stocks.map((stock, i) => ( {event.stocks.map((stock, i) => (
<Tag key={i} size="sm" colorScheme="blue" mb={1}> <Tag key={i} size="sm" colorScheme="blue" mb={1}>
<TagLeftIcon as={FiTrendingUp} /> <TagLeftIcon as={FiTrendingUp} />

View File

@@ -8,7 +8,7 @@
* - label: 显示文案 * - label: 显示文案
*/ */
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef, memo, useCallback } from 'react';
import { import {
Box, Box,
Badge, Badge,
@@ -58,6 +58,135 @@ interface StatusInfo {
text: string; 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 * EventPanel Props
*/ */
@@ -161,105 +290,10 @@ export const EventPanel: React.FC<EventPanelProps> = ({
} }
}; };
// 获取状态信息 // 使用 useCallback 优化回调函数
const getStatusInfo = (status?: EventStatus): StatusInfo => { const handleEdit = useCallback((item: InvestmentEvent) => {
switch (status) { handleOpenModal(item);
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>
);
};
return ( return (
<Box> <Box>
@@ -276,8 +310,20 @@ export const EventPanel: React.FC<EventPanelProps> = ({
</VStack> </VStack>
</Center> </Center>
) : ( ) : (
<Grid templateColumns={{ base: '1fr', md: 'repeat(2, 1fr)' }} gap={4}> <Grid templateColumns={{ base: '1fr', md: 'repeat(2, 1fr)' }} gap={{ base: 3, md: 4 }}>
{events.map(renderCard)} {events.map(event => (
<EventCard
key={event.id}
item={event}
colorScheme={colorScheme}
label={label}
textColor={textColor}
secondaryText={secondaryText}
cardBg={cardBg}
onEdit={handleEdit}
onDelete={handleDelete}
/>
))}
</Grid> </Grid>
)} )}
</VStack> </VStack>

View File

@@ -129,7 +129,6 @@ const InvestmentPlanningCenter: React.FC = () => {
openPlanModalTrigger, openPlanModalTrigger,
openReviewModalTrigger, openReviewModalTrigger,
toast, toast,
bgColor,
borderColor, borderColor,
textColor, textColor,
secondaryText, secondaryText,