From 23dd57366329ae45346feb1d0de6efb1122ed15e Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Mon, 22 Dec 2025 10:21:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0Company=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E7=9A=84UI=E4=B8=BAFUI=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layouts/MainlineTimelineView.js | 519 ++++++++++-------- 1 file changed, 295 insertions(+), 224 deletions(-) diff --git a/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js b/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js index 7d0a55b8..6d1c64e3 100644 --- a/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js +++ b/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js @@ -7,6 +7,7 @@ import React, { forwardRef, useImperativeHandle, useCallback, + useMemo, } from "react"; import { Box, @@ -16,12 +17,11 @@ import { Badge, Flex, Icon, - Collapse, Spinner, Center, IconButton, - useColorModeValue, Tooltip, + Button, } from "@chakra-ui/react"; import { ChevronDownIcon, @@ -32,14 +32,267 @@ import { FiTrendingUp, FiZap } from "react-icons/fi"; import DynamicNewsEventCard from "../../EventCard/DynamicNewsEventCard"; import { getApiBase } from "@utils/apiConfig"; +// 固定深色主题颜色 +const COLORS = { + containerBg: "#1a1d24", + cardBg: "#252a34", + cardBorderColor: "#3a3f4b", + headerHoverBg: "#2d323e", + textColor: "#e2e8f0", + secondaryTextColor: "#a0aec0", + timelineLineColor: "#4299e1", + timelineDotBg: "#63b3ed", + scrollbarTrackBg: "#1a1d24", + scrollbarThumbBg: "#4a5568", + scrollbarThumbHoverBg: "#718096", + statBarBg: "#252a34", +}; + +// 每次加载的事件数量 +const EVENTS_PER_LOAD = 10; + +/** + * 单个主线卡片组件 - 支持懒加载 + */ +const MainlineCard = React.memo(({ + mainline, + colorScheme, + isExpanded, + onToggle, + selectedEvent, + onEventSelect, + eventFollowStatus, + onToggleFollow, + borderColor, +}) => { + // 懒加载状态 + const [displayCount, setDisplayCount] = useState(EVENTS_PER_LOAD); + const [isLoadingMore, setIsLoadingMore] = useState(false); + + // 重置显示数量当折叠时 + useEffect(() => { + if (!isExpanded) { + setDisplayCount(EVENTS_PER_LOAD); + } + }, [isExpanded]); + + // 当前显示的事件 + const displayedEvents = useMemo(() => { + return mainline.events.slice(0, displayCount); + }, [mainline.events, displayCount]); + + // 是否还有更多 + const hasMore = displayCount < mainline.events.length; + + // 加载更多 + const loadMore = useCallback(() => { + setIsLoadingMore(true); + // 使用 setTimeout 模拟异步,避免 UI 卡顿 + setTimeout(() => { + setDisplayCount(prev => Math.min(prev + EVENTS_PER_LOAD, mainline.events.length)); + setIsLoadingMore(false); + }, 50); + }, [mainline.events.length]); + + return ( + + {/* 卡片头部 */} + + + + + {mainline.lv2_name || "其他"} + + + {mainline.event_count} + + + {mainline.lv1_name && ( + + {mainline.lv1_name} + + )} + + + + + {/* 事件列表区域 */} + {isExpanded ? ( + + {/* 时间轴线 */} + + + {/* 事件列表 */} + + {displayedEvents.map((event, eventIndex) => ( + + {/* 时间轴圆点 */} + + {/* 事件卡片 */} + { + onEventSelect?.(clickedEvent); + }} + onTitleClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + onEventSelect?.(event); + }} + onToggleFollow={() => onToggleFollow?.(event.id)} + borderColor={borderColor} + compact + /> + + ))} + + {/* 加载更多按钮 */} + {hasMore && ( + + )} + + + ) : ( + /* 折叠时显示简要信息 */ + + + {mainline.events.slice(0, 3).map((event) => ( + { + e.stopPropagation(); + onEventSelect?.(event); + }} + > + • {event.title} + + ))} + {mainline.events.length > 3 && ( + + ... 还有 {mainline.events.length - 3} 条 + + )} + + + )} + + ); +}); + +MainlineCard.displayName = "MainlineCard"; + /** * 主线时间轴布局组件 - * - * 功能: - * 1. 调用 /api/events/mainline 获取预分组数据 - * 2. 按 lv2 概念分组展示,横向滚动布局 - * 3. 按事件数量从多到少排序 - * 4. 深色背景风格,与列表模式统一 */ const MainlineTimelineViewComponent = forwardRef( ( @@ -60,20 +313,6 @@ const MainlineTimelineViewComponent = forwardRef( const [mainlineData, setMainlineData] = useState(null); const [expandedGroups, setExpandedGroups] = useState({}); - // 深色主题颜色 - const containerBg = useColorModeValue("gray.50", "#1a1d24"); - const cardBg = useColorModeValue("white", "#252a34"); - const cardBorderColor = useColorModeValue("gray.200", "#3a3f4b"); - const headerHoverBg = useColorModeValue("gray.100", "#2d323e"); - const textColor = useColorModeValue("gray.800", "#e2e8f0"); - const secondaryTextColor = useColorModeValue("gray.600", "#a0aec0"); - const timelineLineColor = useColorModeValue("blue.300", "#4299e1"); - const timelineDotBg = useColorModeValue("blue.500", "#63b3ed"); - const scrollbarTrackBg = useColorModeValue("#e2e8f0", "#1a1d24"); - const scrollbarThumbBg = useColorModeValue("#a0aec0", "#4a5568"); - const scrollbarThumbHoverBg = useColorModeValue("#718096", "#718096"); - const statBarBg = useColorModeValue("gray.100", "#252a34"); - // 根据主线类型获取配色 const getColorScheme = useCallback((lv2Name) => { if (!lv2Name) return "gray"; @@ -245,11 +484,11 @@ const MainlineTimelineViewComponent = forwardRef( // 渲染加载状态 if (loading) { return ( - +
- 正在加载主线数据... + 正在加载主线数据...
@@ -259,7 +498,7 @@ const MainlineTimelineViewComponent = forwardRef( // 渲染错误状态 if (error) { return ( - +
加载失败: {error} @@ -278,12 +517,12 @@ const MainlineTimelineViewComponent = forwardRef( // 渲染空状态 if (!mainlineData?.mainlines?.length) { return ( - +
- 暂无主线数据 - + 暂无主线数据 + 尝试调整筛选条件 @@ -304,7 +543,7 @@ const MainlineTimelineViewComponent = forwardRef( display={display} h="100%" w="100%" - bg={containerBg} + bg={COLORS.containerBg} overflow="hidden" > {/* 顶部统计栏 */} @@ -313,18 +552,18 @@ const MainlineTimelineViewComponent = forwardRef( align="center" px={4} py={2} - bg={statBarBg} + bg={COLORS.statBarBg} borderBottomWidth="1px" - borderBottomColor={cardBorderColor} + borderBottomColor={COLORS.cardBorderColor} > - + {mainline_count} 条主线 - + 共 {total_events} 个事件 {ungrouped_count > 0 && ( @@ -340,9 +579,10 @@ const MainlineTimelineViewComponent = forwardRef( icon={} size="sm" variant="ghost" - colorScheme="gray" + color={COLORS.secondaryTextColor} onClick={() => toggleAll(true)} aria-label="全部展开" + _hover={{ bg: COLORS.headerHoverBg }} /> @@ -350,9 +590,10 @@ const MainlineTimelineViewComponent = forwardRef( icon={} size="sm" variant="ghost" - colorScheme="gray" + color={COLORS.secondaryTextColor} onClick={() => toggleAll(false)} aria-label="全部折叠" + _hover={{ bg: COLORS.headerHoverBg }} /> @@ -360,9 +601,10 @@ const MainlineTimelineViewComponent = forwardRef( icon={} size="sm" variant="ghost" - colorScheme="gray" + color={COLORS.secondaryTextColor} onClick={fetchMainlineData} aria-label="刷新" + _hover={{ bg: COLORS.headerHoverBg }} /> @@ -376,14 +618,14 @@ const MainlineTimelineViewComponent = forwardRef( css={{ "&::-webkit-scrollbar": { height: "8px" }, "&::-webkit-scrollbar-track": { - background: scrollbarTrackBg, + background: COLORS.scrollbarTrackBg, }, "&::-webkit-scrollbar-thumb": { - background: scrollbarThumbBg, + background: COLORS.scrollbarThumbBg, borderRadius: "4px", }, "&::-webkit-scrollbar-thumb:hover": { - background: scrollbarThumbHoverBg, + background: COLORS.scrollbarThumbHoverBg, }, }} > @@ -394,191 +636,20 @@ const MainlineTimelineViewComponent = forwardRef( h="100%" minW="max-content" > - {mainlines.map((mainline) => { - const colorScheme = getColorScheme(mainline.lv2_name); - const isExpanded = expandedGroups[mainline.lv2_id]; - - return ( - - {/* 卡片头部 */} - toggleGroup(mainline.lv2_id)} - _hover={{ bg: headerHoverBg }} - transition="all 0.15s" - borderBottomWidth="1px" - borderBottomColor={cardBorderColor} - flexShrink={0} - > - - - - {mainline.lv2_name || "其他"} - - - {mainline.event_count} - - - {mainline.lv1_name && ( - - {mainline.lv1_name} - - )} - - - - - {/* 事件列表 - 可滚动到底 */} - - - {/* 时间轴线 */} - - - {/* 事件列表 */} - - {mainline.events.map((event, eventIndex) => ( - - {/* 时间轴圆点 */} - - {/* 事件卡片 */} - { - onEventSelect?.(clickedEvent); - }} - onTitleClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - onEventSelect?.(event); - }} - onToggleFollow={() => onToggleFollow?.(event.id)} - borderColor={borderColor} - compact - /> - - ))} - - - - - {/* 折叠时显示简要信息 */} - {!isExpanded && ( - - - {mainline.events.slice(0, 3).map((event) => ( - { - e.stopPropagation(); - onEventSelect?.(event); - }} - > - • {event.title} - - ))} - {mainline.events.length > 3 && ( - - ... 还有 {mainline.events.length - 3} 条 - - )} - - - )} - - ); - })} + {mainlines.map((mainline) => ( + toggleGroup(mainline.lv2_id)} + selectedEvent={selectedEvent} + onEventSelect={onEventSelect} + eventFollowStatus={eventFollowStatus} + onToggleFollow={onToggleFollow} + borderColor={borderColor} + /> + ))}