From 317bdb1daf71e89cae0ece3def43de48e67af03f Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Mon, 22 Dec 2025 17:38:56 +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 | 309 +++++++++++------- 1 file changed, 194 insertions(+), 115 deletions(-) diff --git a/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js b/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js index 24dea4ff..49eba932 100644 --- a/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js +++ b/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js @@ -24,12 +24,12 @@ import { Button, } from "@chakra-ui/react"; import { ChevronDownIcon, ChevronUpIcon, RepeatIcon } from "@chakra-ui/icons"; -import { FiTrendingUp, FiZap, FiClock } from "react-icons/fi"; +import { FiTrendingUp, FiZap } from "react-icons/fi"; import { FireOutlined } from "@ant-design/icons"; import dayjs from "dayjs"; import { Select } from "antd"; -import MiniEventCard from "../../EventCard/MiniEventCard"; import { getApiBase } from "@utils/apiConfig"; +import { getChangeColor } from "@utils/colorUtils"; import "../../SearchFilters/CompactSearchBox.css"; // 固定深色主题颜色 @@ -50,7 +50,7 @@ const COLORS = { const EVENTS_PER_LOAD = 12; /** - * 格式化时间显示 + * 格式化时间显示 - 始终显示日期,避免跨天混淆 */ const formatEventTime = (dateStr) => { if (!dateStr) return ""; @@ -59,78 +59,164 @@ const formatEventTime = (dateStr) => { const isToday = date.isSame(now, "day"); const isYesterday = date.isSame(now.subtract(1, "day"), "day"); + // 始终显示日期,用标签区分今天/昨天 if (isToday) { - return date.format("HH:mm"); + return `今天 ${date.format("MM-DD HH:mm")}`; } else if (isYesterday) { - return `昨天 ${date.format("HH:mm")}`; + return `昨天 ${date.format("MM-DD HH:mm")}`; } else { return date.format("MM-DD HH:mm"); } }; /** - * 单个事件项组件 - 带时间轴 + * 根据涨跌幅获取背景色 */ -const TimelineEventItem = React.memo(({ event, isSelected, onEventClick, isHot }) => { - // 使用后端返回的字段:related_max_chg(最大涨幅)或 related_avg_chg(平均涨幅) - const changePct = event.related_max_chg ?? event.related_avg_chg ?? event.max_change_pct ?? event.change_pct; - const hasChange = changePct != null; - const isPositive = hasChange && changePct >= 0; +const getChangeBgColor = (value) => { + if (value == null || isNaN(value)) return "transparent"; + const absChange = Math.abs(value); + if (value > 0) { + if (absChange >= 5) return "rgba(239, 68, 68, 0.12)"; + if (absChange >= 3) return "rgba(239, 68, 68, 0.08)"; + return "rgba(239, 68, 68, 0.05)"; + } else if (value < 0) { + if (absChange >= 5) return "rgba(16, 185, 129, 0.12)"; + if (absChange >= 3) return "rgba(16, 185, 129, 0.08)"; + return "rgba(16, 185, 129, 0.05)"; + } + return "transparent"; +}; + +/** + * 单个事件项组件 - 卡片式布局 + */ +const TimelineEventItem = React.memo(({ event, isSelected, onEventClick }) => { + // 使用 related_max_chg 作为主要涨幅显示 + const maxChange = event.related_max_chg; + const avgChange = event.related_avg_chg; + const hasMaxChange = maxChange != null && !isNaN(maxChange); + const hasAvgChange = avgChange != null && !isNaN(avgChange); + + // 用于背景色的涨幅(使用平均超额) + const bgValue = avgChange; return ( - onEventClick?.(event)} - _hover={{ bg: "rgba(255, 255, 255, 0.06)" }} - borderRadius="md" - transition="all 0.15s" - bg={isSelected ? "rgba(66, 153, 225, 0.15)" : "transparent"} - py={2} - px={1} + bg={isSelected ? "rgba(66, 153, 225, 0.15)" : getChangeBgColor(bgValue)} + borderWidth="1px" + borderColor={isSelected ? "#4299e1" : COLORS.cardBorderColor} + borderRadius="lg" + p={3} + mb={2} + _hover={{ + bg: isSelected ? "rgba(66, 153, 225, 0.2)" : "rgba(255, 255, 255, 0.06)", + borderColor: isSelected ? "#63b3ed" : "#5a6070", + transform: "translateY(-1px)", + }} + transition="all 0.2s ease" > - {/* 左侧时间 */} + {/* 第一行:时间 */} {formatEventTime(event.created_at || event.event_time)} - {/* 右侧内容 */} - - - {event.title} - - + {/* 第二行:标题 */} + + {event.title} + - {/* 涨跌幅 */} - {hasChange && ( - - {isPositive ? "+" : ""} - {changePct.toFixed(1)}% - + {/* 第三行:涨跌幅指标 */} + {(hasMaxChange || hasAvgChange) && ( + + {/* 最大超额 */} + {hasMaxChange && ( + 0 ? "rgba(239, 68, 68, 0.15)" : "rgba(16, 185, 129, 0.15)"} + borderWidth="1px" + borderColor={maxChange > 0 ? "rgba(239, 68, 68, 0.3)" : "rgba(16, 185, 129, 0.3)"} + borderRadius="md" + px={2} + py={1} + > + + 最大超额 + + 0 ? "#fc8181" : "#68d391"} + > + {maxChange > 0 ? "+" : ""}{maxChange.toFixed(2)}% + + + )} + + {/* 平均超额 */} + {hasAvgChange && ( + 0 ? "rgba(239, 68, 68, 0.15)" : "rgba(16, 185, 129, 0.15)"} + borderWidth="1px" + borderColor={avgChange > 0 ? "rgba(239, 68, 68, 0.3)" : "rgba(16, 185, 129, 0.3)"} + borderRadius="md" + px={2} + py={1} + > + + 平均超额 + + 0 ? "#fc8181" : "#68d391"} + > + {avgChange > 0 ? "+" : ""}{avgChange.toFixed(2)}% + + + )} + + {/* 超预期得分 */} + {event.expectation_surprise_score != null && ( + = 60 ? "rgba(239, 68, 68, 0.15)" : + event.expectation_surprise_score >= 40 ? "rgba(237, 137, 54, 0.15)" : "rgba(66, 153, 225, 0.15)"} + borderWidth="1px" + borderColor={event.expectation_surprise_score >= 60 ? "rgba(239, 68, 68, 0.3)" : + event.expectation_surprise_score >= 40 ? "rgba(237, 137, 54, 0.3)" : "rgba(66, 153, 225, 0.3)"} + borderRadius="md" + px={2} + py={1} + > + + 超预期 + + = 60 ? "#fc8181" : + event.expectation_surprise_score >= 40 ? "#ed8936" : "#63b3ed"} + > + {Math.round(event.expectation_surprise_score)}分 + + + )} + )} - + ); }); @@ -159,25 +245,23 @@ const MainlineCard = React.memo( } }, [isExpanded]); - // 找出涨幅最大的事件(HOT 事件) + // 找出最大超额涨幅最高的事件(HOT 事件) const hotEvent = useMemo(() => { if (!mainline.events || mainline.events.length === 0) return null; let maxChange = -Infinity; let hot = null; mainline.events.forEach((event) => { - // 使用后端返回的字段:related_max_chg(最大涨幅) - const change = event.related_max_chg ?? event.related_avg_chg ?? event.max_change_pct ?? event.change_pct ?? -Infinity; + // 统一使用 related_max_chg(最大超额) + const change = event.related_max_chg ?? -Infinity; if (change > maxChange) { maxChange = change; hot = event; } }); - // 只有当有正涨幅时才显示 HOT + // 只有当最大超额 > 0 时才显示 HOT return maxChange > 0 ? hot : null; }, [mainline.events]); - const hotEventId = hotEvent?.id; - // 当前显示的事件 const displayedEvents = useMemo(() => { return mainline.events.slice(0, displayCount); @@ -292,8 +376,8 @@ const MainlineCard = React.memo( {hotEvent && ( - - {/* HOT 标签 */} + {/* 第一行:HOT 标签 + 最大超额 */} + HOT - {/* HOT 事件标题 */} - - {hotEvent.title} - - {/* HOT 事件涨幅 */} - {(hotEvent.related_max_chg ?? hotEvent.related_avg_chg) != null && ( - - +{(hotEvent.related_max_chg ?? hotEvent.related_avg_chg).toFixed(1)}% - + + 最大超额 +{hotEvent.related_max_chg.toFixed(2)}% + + )} + {/* 第二行:标题 */} + + {hotEvent.title} + )} @@ -351,7 +436,7 @@ const MainlineCard = React.memo( {/* 事件列表区域 */} {isExpanded ? ( - {/* 事件列表 - 带时间轴 */} - - {displayedEvents.map((event) => ( - - ))} - + {/* 事件列表 - 卡片式 */} + {displayedEvents.map((event) => ( + + ))} {/* 加载更多按钮 */} {hasMore && ( @@ -389,7 +471,7 @@ const MainlineCard = React.memo( isLoading={isLoadingMore} loadingText="加载中..." w="100%" - mt={2} + mt={1} _hover={{ bg: COLORS.headerHoverBg }} > 加载更多 ({mainline.events.length - displayCount} 条) @@ -397,24 +479,21 @@ const MainlineCard = React.memo( )} ) : ( - /* 折叠时显示简要信息 - 也带时间轴 */ - - - {mainline.events.slice(0, 4).map((event) => ( - - ))} - {mainline.events.length > 4 && ( - - ... 还有 {mainline.events.length - 4} 条 - - )} - + /* 折叠时显示简要信息 - 卡片式 */ + + {mainline.events.slice(0, 3).map((event) => ( + + ))} + {mainline.events.length > 3 && ( + + ... 还有 {mainline.events.length - 3} 条 + + )} )}