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}
+ />
+ ))}