更新Company页面的UI为FUI风格
This commit is contained in:
@@ -24,7 +24,9 @@ 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 } from "react-icons/fi";
|
import { FiTrendingUp, FiZap, FiClock } from "react-icons/fi";
|
||||||
|
import { FireOutlined } from "@ant-design/icons";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import { Select } from "antd";
|
import { Select } from "antd";
|
||||||
import MiniEventCard from "../../EventCard/MiniEventCard";
|
import MiniEventCard from "../../EventCard/MiniEventCard";
|
||||||
import { getApiBase } from "@utils/apiConfig";
|
import { getApiBase } from "@utils/apiConfig";
|
||||||
@@ -47,6 +49,122 @@ const COLORS = {
|
|||||||
// 每次加载的事件数量
|
// 每次加载的事件数量
|
||||||
const EVENTS_PER_LOAD = 12;
|
const EVENTS_PER_LOAD = 12;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化时间显示
|
||||||
|
*/
|
||||||
|
const formatEventTime = (dateStr) => {
|
||||||
|
if (!dateStr) return "";
|
||||||
|
const date = dayjs(dateStr);
|
||||||
|
const now = dayjs();
|
||||||
|
const isToday = date.isSame(now, "day");
|
||||||
|
const isYesterday = date.isSame(now.subtract(1, "day"), "day");
|
||||||
|
|
||||||
|
if (isToday) {
|
||||||
|
return date.format("HH:mm");
|
||||||
|
} else if (isYesterday) {
|
||||||
|
return `昨天 ${date.format("HH:mm")}`;
|
||||||
|
} else {
|
||||||
|
return date.format("MM-DD HH:mm");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单个事件项组件 - 带时间轴
|
||||||
|
*/
|
||||||
|
const TimelineEventItem = React.memo(({ event, isSelected, onEventClick, isHot }) => {
|
||||||
|
const changePct = event.max_change_pct ?? event.change_pct;
|
||||||
|
const hasChange = changePct != null;
|
||||||
|
const isPositive = hasChange && changePct >= 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
spacing={0}
|
||||||
|
align="stretch"
|
||||||
|
w="100%"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => onEventClick?.(event)}
|
||||||
|
_hover={{ bg: "rgba(255, 255, 255, 0.05)" }}
|
||||||
|
borderRadius="md"
|
||||||
|
transition="all 0.15s"
|
||||||
|
bg={isSelected ? "rgba(66, 153, 225, 0.15)" : "transparent"}
|
||||||
|
>
|
||||||
|
{/* 左侧时间轴 */}
|
||||||
|
<VStack spacing={0} align="center" w="50px" flexShrink={0} position="relative">
|
||||||
|
{/* 时间显示 */}
|
||||||
|
<Text fontSize="xs" color={COLORS.secondaryTextColor} fontWeight="500">
|
||||||
|
{formatEventTime(event.created_at || event.event_time)}
|
||||||
|
</Text>
|
||||||
|
{/* 时间轴线 */}
|
||||||
|
<Box
|
||||||
|
w="2px"
|
||||||
|
flex={1}
|
||||||
|
bg="rgba(255, 255, 255, 0.1)"
|
||||||
|
mt={1}
|
||||||
|
minH="20px"
|
||||||
|
/>
|
||||||
|
{/* 时间轴圆点 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top="18px"
|
||||||
|
w="6px"
|
||||||
|
h="6px"
|
||||||
|
borderRadius="full"
|
||||||
|
bg={isHot ? "#f56565" : `${COLORS.secondaryTextColor}`}
|
||||||
|
border="2px solid"
|
||||||
|
borderColor={COLORS.cardBg}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
{/* 右侧内容 */}
|
||||||
|
<Box flex={1} py={1} pr={2} minW={0}>
|
||||||
|
<HStack spacing={1} align="start">
|
||||||
|
{/* HOT 标签 */}
|
||||||
|
{isHot && (
|
||||||
|
<Badge
|
||||||
|
bg="linear-gradient(135deg, #f56565 0%, #ed8936 100%)"
|
||||||
|
color="white"
|
||||||
|
fontSize="10px"
|
||||||
|
px={1}
|
||||||
|
py={0}
|
||||||
|
borderRadius="sm"
|
||||||
|
flexShrink={0}
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
gap="2px"
|
||||||
|
>
|
||||||
|
<FireOutlined style={{ fontSize: 10 }} />
|
||||||
|
HOT
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
color={COLORS.textColor}
|
||||||
|
noOfLines={2}
|
||||||
|
flex={1}
|
||||||
|
lineHeight="1.4"
|
||||||
|
>
|
||||||
|
{event.title}
|
||||||
|
</Text>
|
||||||
|
{/* 涨跌幅 */}
|
||||||
|
{hasChange && (
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={isPositive ? "#fc8181" : "#68d391"}
|
||||||
|
flexShrink={0}
|
||||||
|
>
|
||||||
|
{isPositive ? "+" : ""}
|
||||||
|
{changePct.toFixed(1)}%
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TimelineEventItem.displayName = "TimelineEventItem";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单个主线卡片组件 - 支持懒加载
|
* 单个主线卡片组件 - 支持懒加载
|
||||||
*/
|
*/
|
||||||
@@ -70,6 +188,23 @@ const MainlineCard = React.memo(
|
|||||||
}
|
}
|
||||||
}, [isExpanded]);
|
}, [isExpanded]);
|
||||||
|
|
||||||
|
// 找出涨幅最大的事件(HOT 事件)
|
||||||
|
const hotEvent = useMemo(() => {
|
||||||
|
if (!mainline.events || mainline.events.length === 0) return null;
|
||||||
|
let maxChange = -Infinity;
|
||||||
|
let hot = null;
|
||||||
|
mainline.events.forEach((event) => {
|
||||||
|
const change = event.max_change_pct ?? event.change_pct ?? -Infinity;
|
||||||
|
if (change > maxChange) {
|
||||||
|
maxChange = change;
|
||||||
|
hot = event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return hot;
|
||||||
|
}, [mainline.events]);
|
||||||
|
|
||||||
|
const hotEventId = hotEvent?.id;
|
||||||
|
|
||||||
// 当前显示的事件
|
// 当前显示的事件
|
||||||
const displayedEvents = useMemo(() => {
|
const displayedEvents = useMemo(() => {
|
||||||
return mainline.events.slice(0, displayCount);
|
return mainline.events.slice(0, displayCount);
|
||||||
@@ -114,6 +249,8 @@ const MainlineCard = React.memo(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 卡片头部 */}
|
{/* 卡片头部 */}
|
||||||
|
<Box flexShrink={0}>
|
||||||
|
{/* 第一行:概念名称 + 涨跌幅 + 事件数 */}
|
||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
@@ -123,9 +260,6 @@ const MainlineCard = React.memo(
|
|||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
_hover={{ bg: COLORS.headerHoverBg }}
|
_hover={{ bg: COLORS.headerHoverBg }}
|
||||||
transition="all 0.15s"
|
transition="all 0.15s"
|
||||||
borderBottomWidth="1px"
|
|
||||||
borderBottomColor={COLORS.cardBorderColor}
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
>
|
||||||
<VStack align="start" spacing={0} flex={1} minW={0}>
|
<VStack align="start" spacing={0} flex={1} minW={0}>
|
||||||
<HStack spacing={2} w="100%">
|
<HStack spacing={2} w="100%">
|
||||||
@@ -181,10 +315,69 @@ const MainlineCard = React.memo(
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
{/* HOT 事件展示区域 */}
|
||||||
|
{hotEvent && (
|
||||||
|
<Box
|
||||||
|
px={3}
|
||||||
|
py={2}
|
||||||
|
bg="rgba(245, 101, 101, 0.08)"
|
||||||
|
borderBottomWidth="1px"
|
||||||
|
borderBottomColor={COLORS.cardBorderColor}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEventSelect?.(hotEvent);
|
||||||
|
}}
|
||||||
|
_hover={{ bg: "rgba(245, 101, 101, 0.15)" }}
|
||||||
|
transition="all 0.15s"
|
||||||
|
>
|
||||||
|
<HStack spacing={2} align="start">
|
||||||
|
{/* HOT 标签 */}
|
||||||
|
<Badge
|
||||||
|
bg="linear-gradient(135deg, #f56565 0%, #ed8936 100%)"
|
||||||
|
color="white"
|
||||||
|
fontSize="10px"
|
||||||
|
px={1.5}
|
||||||
|
py={0.5}
|
||||||
|
borderRadius="sm"
|
||||||
|
flexShrink={0}
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
gap="2px"
|
||||||
|
>
|
||||||
|
<FireOutlined style={{ fontSize: 10 }} />
|
||||||
|
HOT
|
||||||
|
</Badge>
|
||||||
|
{/* HOT 事件标题 */}
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
color={COLORS.textColor}
|
||||||
|
noOfLines={2}
|
||||||
|
flex={1}
|
||||||
|
lineHeight="1.4"
|
||||||
|
>
|
||||||
|
{hotEvent.title}
|
||||||
|
</Text>
|
||||||
|
{/* HOT 事件涨幅 */}
|
||||||
|
{(hotEvent.max_change_pct ?? hotEvent.change_pct) != null && (
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="#fc8181"
|
||||||
|
flexShrink={0}
|
||||||
|
>
|
||||||
|
+{(hotEvent.max_change_pct ?? hotEvent.change_pct).toFixed(1)}%
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* 事件列表区域 */}
|
{/* 事件列表区域 */}
|
||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
<Box
|
<Box
|
||||||
px={2}
|
px={1}
|
||||||
py={2}
|
py={2}
|
||||||
flex={1}
|
flex={1}
|
||||||
overflowY="auto"
|
overflowY="auto"
|
||||||
@@ -199,14 +392,15 @@ const MainlineCard = React.memo(
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 事件列表 - 单列布局 */}
|
{/* 事件列表 - 带时间轴 */}
|
||||||
<VStack spacing={2} align="stretch">
|
<VStack spacing={0} align="stretch">
|
||||||
{displayedEvents.map((event) => (
|
{displayedEvents.map((event) => (
|
||||||
<MiniEventCard
|
<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>
|
</VStack>
|
||||||
@@ -229,27 +423,20 @@ const MainlineCard = React.memo(
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
/* 折叠时显示简要信息 */
|
/* 折叠时显示简要信息 - 也带时间轴 */
|
||||||
<Box px={3} py={2} flex={1} overflow="hidden">
|
<Box px={1} py={2} flex={1} overflow="hidden">
|
||||||
<VStack spacing={1} align="stretch">
|
<VStack spacing={0} align="stretch">
|
||||||
{mainline.events.slice(0, 4).map((event) => (
|
{mainline.events.slice(0, 4).map((event) => (
|
||||||
<Text
|
<TimelineEventItem
|
||||||
key={event.id}
|
key={event.id}
|
||||||
fontSize="xs"
|
event={event}
|
||||||
color={COLORS.secondaryTextColor}
|
isSelected={selectedEvent?.id === event.id}
|
||||||
noOfLines={1}
|
onEventClick={onEventSelect}
|
||||||
cursor="pointer"
|
isHot={event.id === hotEventId}
|
||||||
_hover={{ color: COLORS.textColor }}
|
/>
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onEventSelect?.(event);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
• {event.title}
|
|
||||||
</Text>
|
|
||||||
))}
|
))}
|
||||||
{mainline.events.length > 4 && (
|
{mainline.events.length > 4 && (
|
||||||
<Text fontSize="xs" color={COLORS.secondaryTextColor}>
|
<Text fontSize="xs" color={COLORS.secondaryTextColor} pl="50px" pt={1}>
|
||||||
... 还有 {mainline.events.length - 4} 条
|
... 还有 {mainline.events.length - 4} 条
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user