From e93d5532bf2f65e3090605f84c7075ff6ab37d38 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 23 Dec 2025 10:52:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(Center):=20=E6=96=B0=E5=A2=9E=20FUIEventCa?= =?UTF-8?q?rd=20=E6=AF=9B=E7=8E=BB=E7=92=83=E9=A3=8E=E6=A0=BC=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E5=8D=A1=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 融合 ReviewCard 的 UI 风格(毛玻璃 + 金色主题) - 支持编辑、删除、展开描述等功能 - 使用 FUI_THEME 常量统一管理主题色 - 用于复盘列表的高级视觉呈现 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/views/Center/components/FUIEventCard.tsx | 255 +++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 src/views/Center/components/FUIEventCard.tsx diff --git a/src/views/Center/components/FUIEventCard.tsx b/src/views/Center/components/FUIEventCard.tsx new file mode 100644 index 00000000..765bb6c6 --- /dev/null +++ b/src/views/Center/components/FUIEventCard.tsx @@ -0,0 +1,255 @@ +/** + * FUIEventCard - 毛玻璃风格投资事件卡片组件 + * + * 融合 ReviewCard 的 UI 风格(毛玻璃 + 金色主题) + * 与 EventCard 的功能(编辑、删除、展开描述) + * + * 用于复盘列表的高级视觉呈现 + */ + +import React, { useState, useEffect, useRef, memo } from 'react'; +import { + Box, + IconButton, + Flex, + VStack, + HStack, + Text, + Tag, + TagLabel, + TagLeftIcon, + Button, +} from '@chakra-ui/react'; +import { + FiEdit2, + FiTrash2, + FiCalendar, + FiTrendingUp, + FiChevronDown, + FiChevronUp, +} from 'react-icons/fi'; +import { FileText, Heart, Target } from 'lucide-react'; +import dayjs from 'dayjs'; +import 'dayjs/locale/zh-cn'; + +import type { InvestmentEvent } from '@/types'; + +dayjs.locale('zh-cn'); + +// 主题颜色常量(与 ReviewCard 保持一致) +const FUI_THEME = { + bg: 'rgba(26, 26, 46, 0.7)', + border: 'rgba(212, 175, 55, 0.15)', + borderHover: 'rgba(212, 175, 55, 0.3)', + text: { + primary: 'rgba(255, 255, 255, 0.95)', + secondary: 'rgba(255, 255, 255, 0.6)', + muted: 'rgba(255, 255, 255, 0.4)', + }, + accent: '#D4AF37', // 金色 + icon: '#F59E0B', // 橙色图标 +}; + +/** + * FUIEventCard Props + */ +export interface FUIEventCardProps { + /** 事件数据 */ + event: InvestmentEvent; + /** 主题颜色 */ + colorScheme?: string; + /** 显示标签(用于 aria-label) */ + label?: string; + /** 编辑回调 */ + onEdit?: (event: InvestmentEvent) => void; + /** 删除回调 */ + onDelete?: (id: number) => void; +} + +/** 描述最大显示行数 */ +const MAX_LINES = 3; + +/** + * FUIEventCard 组件 + */ +export const FUIEventCard = memo(({ + event, + colorScheme = 'orange', + label = '复盘', + onEdit, + onDelete, +}) => { + // 展开/收起状态 + const [isExpanded, setIsExpanded] = useState(false); + const [isOverflow, setIsOverflow] = useState(false); + const descriptionRef = useRef(null); + + // 获取描述内容 + const description = event.description || event.content || ''; + + // 检测描述是否溢出 + useEffect(() => { + const el = descriptionRef.current; + if (el && description) { + const lineHeight = parseInt(getComputedStyle(el).lineHeight) || 20; + const maxHeight = lineHeight * MAX_LINES; + setIsOverflow(el.scrollHeight > maxHeight + 5); + } else { + setIsOverflow(false); + } + }, [description]); + + return ( + + + {/* 头部区域:图标 + 标题 + 操作按钮 */} + + + + + [{event.title}] + + + + {/* 编辑/删除按钮 */} + {(onEdit || onDelete) && ( + + {onEdit && ( + } + size="xs" + variant="ghost" + color={FUI_THEME.text.secondary} + _hover={{ color: FUI_THEME.accent, bg: 'rgba(212, 175, 55, 0.1)' }} + onClick={() => onEdit(event)} + aria-label={`编辑${label}`} + /> + )} + {onDelete && ( + } + size="xs" + variant="ghost" + color={FUI_THEME.text.secondary} + _hover={{ color: '#EF4444', bg: 'rgba(239, 68, 68, 0.1)' }} + onClick={() => onDelete(event.id)} + aria-label={`删除${label}`} + /> + )} + + )} + + + {/* 日期行 */} + + + + {dayjs(event.event_date || event.date).format('YYYY年MM月DD日')} + + + + {/* 描述内容(可展开/收起) */} + {description && ( + + + + {event.type === 'plan' ? ( + <> + + + 计划内容: + + + ) : ( + <> + + + 心得: + + + )} + + + {description} + + {isOverflow && ( + + )} + + )} + + {/* 股票标签 */} + {event.stocks && event.stocks.length > 0 && ( + + + 相关股票: + + {event.stocks.map((stock, idx) => { + const stockCode = typeof stock === 'string' ? stock : stock.code; + const displayText = typeof stock === 'string' ? stock : `${stock.name}(${stock.code})`; + return ( + + + {displayText} + + ); + })} + + )} + + + ); +}); + +FUIEventCard.displayName = 'FUIEventCard'; + +export default FUIEventCard;