perf: EventPanel 性能优化,EventDetailCard H5适配,清理冗余类型
This commit is contained in:
@@ -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} />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -129,7 +129,6 @@ const InvestmentPlanningCenter: React.FC = () => {
|
|||||||
openPlanModalTrigger,
|
openPlanModalTrigger,
|
||||||
openReviewModalTrigger,
|
openReviewModalTrigger,
|
||||||
toast,
|
toast,
|
||||||
bgColor,
|
|
||||||
borderColor,
|
borderColor,
|
||||||
textColor,
|
textColor,
|
||||||
secondaryText,
|
secondaryText,
|
||||||
|
|||||||
Reference in New Issue
Block a user