更新Company页面的UI为FUI风格

This commit is contained in:
2025-12-22 17:38:56 +08:00
parent 5843029b9c
commit 317bdb1daf

View File

@@ -24,12 +24,12 @@ import {
Button, Button,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { ChevronDownIcon, ChevronUpIcon, RepeatIcon } from "@chakra-ui/icons"; 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 { FireOutlined } from "@ant-design/icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Select } from "antd"; import { Select } from "antd";
import MiniEventCard from "../../EventCard/MiniEventCard";
import { getApiBase } from "@utils/apiConfig"; import { getApiBase } from "@utils/apiConfig";
import { getChangeColor } from "@utils/colorUtils";
import "../../SearchFilters/CompactSearchBox.css"; import "../../SearchFilters/CompactSearchBox.css";
// 固定深色主题颜色 // 固定深色主题颜色
@@ -50,7 +50,7 @@ const COLORS = {
const EVENTS_PER_LOAD = 12; const EVENTS_PER_LOAD = 12;
/** /**
* 格式化时间显示 * 格式化时间显示 - 始终显示日期,避免跨天混淆
*/ */
const formatEventTime = (dateStr) => { const formatEventTime = (dateStr) => {
if (!dateStr) return ""; if (!dateStr) return "";
@@ -59,78 +59,164 @@ const formatEventTime = (dateStr) => {
const isToday = date.isSame(now, "day"); const isToday = date.isSame(now, "day");
const isYesterday = date.isSame(now.subtract(1, "day"), "day"); const isYesterday = date.isSame(now.subtract(1, "day"), "day");
// 始终显示日期,用标签区分今天/昨天
if (isToday) { if (isToday) {
return date.format("HH:mm"); return `今天 ${date.format("MM-DD HH:mm")}`;
} else if (isYesterday) { } else if (isYesterday) {
return `昨天 ${date.format("HH:mm")}`; return `昨天 ${date.format("MM-DD HH:mm")}`;
} else { } else {
return date.format("MM-DD HH:mm"); return date.format("MM-DD HH:mm");
} }
}; };
/** /**
* 单个事件项组件 - 带时间轴 * 根据涨跌幅获取背景色
*/ */
const TimelineEventItem = React.memo(({ event, isSelected, onEventClick, isHot }) => { const getChangeBgColor = (value) => {
// 使用后端返回的字段related_max_chg最大涨幅或 related_avg_chg平均涨幅 if (value == null || isNaN(value)) return "transparent";
const changePct = event.related_max_chg ?? event.related_avg_chg ?? event.max_change_pct ?? event.change_pct; const absChange = Math.abs(value);
const hasChange = changePct != null; if (value > 0) {
const isPositive = hasChange && changePct >= 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 ( return (
<HStack <Box
spacing={0}
align="flex-start"
w="100%" w="100%"
cursor="pointer" cursor="pointer"
onClick={() => onEventClick?.(event)} onClick={() => onEventClick?.(event)}
_hover={{ bg: "rgba(255, 255, 255, 0.06)" }} bg={isSelected ? "rgba(66, 153, 225, 0.15)" : getChangeBgColor(bgValue)}
borderRadius="md" borderWidth="1px"
transition="all 0.15s" borderColor={isSelected ? "#4299e1" : COLORS.cardBorderColor}
bg={isSelected ? "rgba(66, 153, 225, 0.15)" : "transparent"} borderRadius="lg"
py={2} p={3}
px={1} 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"
> >
{/* 左侧时间 */} {/* 第一行:时间 */}
<Text <Text
fontSize="sm" fontSize="xs"
color={COLORS.secondaryTextColor} color={COLORS.secondaryTextColor}
fontWeight="500" mb={1.5}
w="58px"
flexShrink={0}
textAlign="right"
pr={3}
> >
{formatEventTime(event.created_at || event.event_time)} {formatEventTime(event.created_at || event.event_time)}
</Text> </Text>
{/* 右侧内容 */} {/* 第二行:标题 */}
<Box flex={1} minW={0} pr={2}> <Text
<Text fontSize="sm"
fontSize="sm" color="#63b3ed"
color={COLORS.textColor} fontWeight="medium"
noOfLines={2} noOfLines={2}
lineHeight="1.6" lineHeight="1.5"
> mb={2}
{event.title} _hover={{ textDecoration: "underline" }}
</Text> >
</Box> {event.title}
</Text>
{/* 涨跌幅 */} {/* 第三行:涨跌幅指标 */}
{hasChange && ( {(hasMaxChange || hasAvgChange) && (
<Text <HStack spacing={2} flexWrap="wrap">
fontSize="sm" {/* 最大超额 */}
fontWeight="bold" {hasMaxChange && (
color={isPositive ? "#fc8181" : "#68d391"} <Box
flexShrink={0} bg={maxChange > 0 ? "rgba(239, 68, 68, 0.15)" : "rgba(16, 185, 129, 0.15)"}
minW="50px" borderWidth="1px"
textAlign="right" borderColor={maxChange > 0 ? "rgba(239, 68, 68, 0.3)" : "rgba(16, 185, 129, 0.3)"}
> borderRadius="md"
{isPositive ? "+" : ""} px={2}
{changePct.toFixed(1)}% py={1}
</Text> >
<Text fontSize="xs" color={COLORS.secondaryTextColor} mb={0.5}>
最大超额
</Text>
<Text
fontSize="sm"
fontWeight="bold"
color={maxChange > 0 ? "#fc8181" : "#68d391"}
>
{maxChange > 0 ? "+" : ""}{maxChange.toFixed(2)}%
</Text>
</Box>
)}
{/* 平均超额 */}
{hasAvgChange && (
<Box
bg={avgChange > 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}
>
<Text fontSize="xs" color={COLORS.secondaryTextColor} mb={0.5}>
平均超额
</Text>
<Text
fontSize="sm"
fontWeight="bold"
color={avgChange > 0 ? "#fc8181" : "#68d391"}
>
{avgChange > 0 ? "+" : ""}{avgChange.toFixed(2)}%
</Text>
</Box>
)}
{/* 超预期得分 */}
{event.expectation_surprise_score != null && (
<Box
bg={event.expectation_surprise_score >= 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}
>
<Text fontSize="xs" color={COLORS.secondaryTextColor} mb={0.5}>
超预期
</Text>
<Text
fontSize="sm"
fontWeight="bold"
color={event.expectation_surprise_score >= 60 ? "#fc8181" :
event.expectation_surprise_score >= 40 ? "#ed8936" : "#63b3ed"}
>
{Math.round(event.expectation_surprise_score)}
</Text>
</Box>
)}
</HStack>
)} )}
</HStack> </Box>
); );
}); });
@@ -159,25 +245,23 @@ const MainlineCard = React.memo(
} }
}, [isExpanded]); }, [isExpanded]);
// 找出涨幅最的事件HOT 事件) // 找出最大超额涨幅最的事件HOT 事件)
const hotEvent = useMemo(() => { const hotEvent = useMemo(() => {
if (!mainline.events || mainline.events.length === 0) return null; if (!mainline.events || mainline.events.length === 0) return null;
let maxChange = -Infinity; let maxChange = -Infinity;
let hot = null; let hot = null;
mainline.events.forEach((event) => { mainline.events.forEach((event) => {
// 使用后端返回的字段:related_max_chg最大涨幅 // 统一使用 related_max_chg最大超额
const change = event.related_max_chg ?? event.related_avg_chg ?? event.max_change_pct ?? event.change_pct ?? -Infinity; const change = event.related_max_chg ?? -Infinity;
if (change > maxChange) { if (change > maxChange) {
maxChange = change; maxChange = change;
hot = event; hot = event;
} }
}); });
// 只有当有正涨幅时才显示 HOT // 只有当最大超额 > 0 时才显示 HOT
return maxChange > 0 ? hot : null; return maxChange > 0 ? hot : null;
}, [mainline.events]); }, [mainline.events]);
const hotEventId = hotEvent?.id;
// 当前显示的事件 // 当前显示的事件
const displayedEvents = useMemo(() => { const displayedEvents = useMemo(() => {
return mainline.events.slice(0, displayCount); return mainline.events.slice(0, displayCount);
@@ -292,8 +376,8 @@ const MainlineCard = React.memo(
{hotEvent && ( {hotEvent && (
<Box <Box
px={3} px={3}
py={2.5} py={3}
bg="rgba(245, 101, 101, 0.08)" bg="rgba(245, 101, 101, 0.1)"
borderBottomWidth="1px" borderBottomWidth="1px"
borderBottomColor={COLORS.cardBorderColor} borderBottomColor={COLORS.cardBorderColor}
cursor="pointer" cursor="pointer"
@@ -301,11 +385,11 @@ const MainlineCard = React.memo(
e.stopPropagation(); e.stopPropagation();
onEventSelect?.(hotEvent); onEventSelect?.(hotEvent);
}} }}
_hover={{ bg: "rgba(245, 101, 101, 0.15)" }} _hover={{ bg: "rgba(245, 101, 101, 0.18)" }}
transition="all 0.15s" transition="all 0.15s"
> >
<HStack spacing={2} align="start"> {/* 第一行HOT 标签 + 最大超额 */}
{/* HOT 标签 */} <HStack spacing={2} mb={1.5}>
<Badge <Badge
bg="linear-gradient(135deg, #f56565 0%, #ed8936 100%)" bg="linear-gradient(135deg, #f56565 0%, #ed8936 100%)"
color="white" color="white"
@@ -313,7 +397,6 @@ const MainlineCard = React.memo(
px={2} px={2}
py={0.5} py={0.5}
borderRadius="sm" borderRadius="sm"
flexShrink={0}
display="flex" display="flex"
alignItems="center" alignItems="center"
gap="3px" gap="3px"
@@ -322,28 +405,30 @@ const MainlineCard = React.memo(
<FireOutlined style={{ fontSize: 11 }} /> <FireOutlined style={{ fontSize: 11 }} />
HOT HOT
</Badge> </Badge>
{/* HOT 事件标题 */} {/* 最大超额涨幅 */}
<Text {hotEvent.related_max_chg != null && (
fontSize="sm" <Box
color={COLORS.textColor} bg="rgba(239, 68, 68, 0.2)"
noOfLines={2} borderRadius="md"
flex={1} px={2}
lineHeight="1.5" py={0.5}
>
{hotEvent.title}
</Text>
{/* HOT 事件涨幅 */}
{(hotEvent.related_max_chg ?? hotEvent.related_avg_chg) != null && (
<Text
fontSize="sm"
fontWeight="bold"
color="#fc8181"
flexShrink={0}
> >
+{(hotEvent.related_max_chg ?? hotEvent.related_avg_chg).toFixed(1)}% <Text fontSize="xs" color="#fc8181" fontWeight="bold">
</Text> 最大超额 +{hotEvent.related_max_chg.toFixed(2)}%
</Text>
</Box>
)} )}
</HStack> </HStack>
{/* 第二行:标题 */}
<Text
fontSize="sm"
color={COLORS.textColor}
noOfLines={2}
lineHeight="1.5"
fontWeight="medium"
>
{hotEvent.title}
</Text>
</Box> </Box>
)} )}
</Box> </Box>
@@ -351,7 +436,7 @@ const MainlineCard = React.memo(
{/* 事件列表区域 */} {/* 事件列表区域 */}
{isExpanded ? ( {isExpanded ? (
<Box <Box
px={1} px={2}
py={2} py={2}
flex={1} flex={1}
overflowY="auto" overflowY="auto"
@@ -366,18 +451,15 @@ const MainlineCard = React.memo(
}, },
}} }}
> >
{/* 事件列表 - 带时间轴 */} {/* 事件列表 - 卡片式 */}
<VStack spacing={0} align="stretch"> {displayedEvents.map((event) => (
{displayedEvents.map((event) => ( <TimelineEventItem
<TimelineEventItem key={event.id}
key={event.id} event={event}
event={event} isSelected={selectedEvent?.id === event.id}
isSelected={selectedEvent?.id === event.id} onEventClick={onEventSelect}
onEventClick={onEventSelect} />
isHot={event.id === hotEventId} ))}
/>
))}
</VStack>
{/* 加载更多按钮 */} {/* 加载更多按钮 */}
{hasMore && ( {hasMore && (
@@ -389,7 +471,7 @@ const MainlineCard = React.memo(
isLoading={isLoadingMore} isLoading={isLoadingMore}
loadingText="加载中..." loadingText="加载中..."
w="100%" w="100%"
mt={2} mt={1}
_hover={{ bg: COLORS.headerHoverBg }} _hover={{ bg: COLORS.headerHoverBg }}
> >
加载更多 ({mainline.events.length - displayCount} ) 加载更多 ({mainline.events.length - displayCount} )
@@ -397,24 +479,21 @@ const MainlineCard = React.memo(
)} )}
</Box> </Box>
) : ( ) : (
/* 折叠时显示简要信息 - 也带时间轴 */ /* 折叠时显示简要信息 - 卡片式 */
<Box px={1} py={2} flex={1} overflow="hidden"> <Box px={2} py={2} flex={1} overflow="hidden">
<VStack spacing={0} align="stretch"> {mainline.events.slice(0, 3).map((event) => (
{mainline.events.slice(0, 4).map((event) => ( <TimelineEventItem
<TimelineEventItem key={event.id}
key={event.id} event={event}
event={event} isSelected={selectedEvent?.id === event.id}
isSelected={selectedEvent?.id === event.id} onEventClick={onEventSelect}
onEventClick={onEventSelect} />
isHot={event.id === hotEventId} ))}
/> {mainline.events.length > 3 && (
))} <Text fontSize="sm" color={COLORS.secondaryTextColor} textAlign="center" pt={1}>
{mainline.events.length > 4 && ( ... 还有 {mainline.events.length - 3}
<Text fontSize="xs" color={COLORS.secondaryTextColor} pl="50px" pt={1}> </Text>
... 还有 {mainline.events.length - 4} )}
</Text>
)}
</VStack>
</Box> </Box>
)} )}
</Box> </Box>