style(DynamicTracking): 应用黑金主题
- NewsEventsTab: 添加黑金主题配色系统 - ForecastPanel: 业绩预告面板黑金样式 - NewsPanel: 切换 blackGold 主题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,58 @@ import {
|
|||||||
FaChevronRight,
|
FaChevronRight,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
|
|
||||||
|
// 黑金主题配色
|
||||||
|
const THEME_PRESETS = {
|
||||||
|
blackGold: {
|
||||||
|
bg: "#0A0E17",
|
||||||
|
cardBg: "#1A1F2E",
|
||||||
|
cardHoverBg: "#212633",
|
||||||
|
cardBorder: "rgba(212, 175, 55, 0.2)",
|
||||||
|
cardHoverBorder: "#D4AF37",
|
||||||
|
textPrimary: "#E8E9ED",
|
||||||
|
textSecondary: "#A0A4B8",
|
||||||
|
textMuted: "#6B7280",
|
||||||
|
gold: "#D4AF37",
|
||||||
|
goldLight: "#FFD54F",
|
||||||
|
inputBg: "#151922",
|
||||||
|
inputBorder: "#2D3748",
|
||||||
|
buttonBg: "#D4AF37",
|
||||||
|
buttonText: "#0A0E17",
|
||||||
|
buttonHoverBg: "#FFD54F",
|
||||||
|
badgeS: { bg: "rgba(255, 195, 0, 0.2)", color: "#FFD54F" },
|
||||||
|
badgeA: { bg: "rgba(249, 115, 22, 0.2)", color: "#FB923C" },
|
||||||
|
badgeB: { bg: "rgba(59, 130, 246, 0.2)", color: "#60A5FA" },
|
||||||
|
badgeC: { bg: "rgba(107, 114, 128, 0.2)", color: "#9CA3AF" },
|
||||||
|
tagBg: "rgba(212, 175, 55, 0.15)",
|
||||||
|
tagColor: "#D4AF37",
|
||||||
|
spinnerColor: "#D4AF37",
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
bg: "white",
|
||||||
|
cardBg: "white",
|
||||||
|
cardHoverBg: "gray.50",
|
||||||
|
cardBorder: "gray.200",
|
||||||
|
cardHoverBorder: "blue.300",
|
||||||
|
textPrimary: "gray.800",
|
||||||
|
textSecondary: "gray.600",
|
||||||
|
textMuted: "gray.500",
|
||||||
|
gold: "blue.500",
|
||||||
|
goldLight: "blue.400",
|
||||||
|
inputBg: "white",
|
||||||
|
inputBorder: "gray.200",
|
||||||
|
buttonBg: "blue.500",
|
||||||
|
buttonText: "white",
|
||||||
|
buttonHoverBg: "blue.600",
|
||||||
|
badgeS: { bg: "red.100", color: "red.600" },
|
||||||
|
badgeA: { bg: "orange.100", color: "orange.600" },
|
||||||
|
badgeB: { bg: "yellow.100", color: "yellow.600" },
|
||||||
|
badgeC: { bg: "green.100", color: "green.600" },
|
||||||
|
tagBg: "cyan.50",
|
||||||
|
tagColor: "cyan.600",
|
||||||
|
spinnerColor: "blue.500",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新闻动态 Tab 组件
|
* 新闻动态 Tab 组件
|
||||||
*
|
*
|
||||||
@@ -48,6 +100,7 @@ import {
|
|||||||
* - onSearch: 搜索提交回调 () => void
|
* - onSearch: 搜索提交回调 () => void
|
||||||
* - onPageChange: 分页回调 (page) => void
|
* - onPageChange: 分页回调 (page) => void
|
||||||
* - cardBg: 卡片背景色
|
* - cardBg: 卡片背景色
|
||||||
|
* - themePreset: 主题预设 'blackGold' | 'default'
|
||||||
*/
|
*/
|
||||||
const NewsEventsTab = ({
|
const NewsEventsTab = ({
|
||||||
newsEvents = [],
|
newsEvents = [],
|
||||||
@@ -65,7 +118,11 @@ const NewsEventsTab = ({
|
|||||||
onSearch,
|
onSearch,
|
||||||
onPageChange,
|
onPageChange,
|
||||||
cardBg,
|
cardBg,
|
||||||
|
themePreset = "default",
|
||||||
}) => {
|
}) => {
|
||||||
|
// 获取主题配色
|
||||||
|
const theme = THEME_PRESETS[themePreset] || THEME_PRESETS.default;
|
||||||
|
const isBlackGold = themePreset === "blackGold";
|
||||||
// 事件类型图标映射
|
// 事件类型图标映射
|
||||||
const getEventTypeIcon = (eventType) => {
|
const getEventTypeIcon = (eventType) => {
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
@@ -80,15 +137,25 @@ const NewsEventsTab = ({
|
|||||||
return iconMap[eventType] || FaNewspaper;
|
return iconMap[eventType] || FaNewspaper;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重要性颜色映射
|
// 重要性颜色映射 - 根据主题返回不同配色
|
||||||
const getImportanceColor = (importance) => {
|
const getImportanceBadgeStyle = (importance) => {
|
||||||
|
if (isBlackGold) {
|
||||||
|
const styles = {
|
||||||
|
S: theme.badgeS,
|
||||||
|
A: theme.badgeA,
|
||||||
|
B: theme.badgeB,
|
||||||
|
C: theme.badgeC,
|
||||||
|
};
|
||||||
|
return styles[importance] || { bg: "rgba(107, 114, 128, 0.2)", color: "#9CA3AF" };
|
||||||
|
}
|
||||||
|
// 默认主题使用 colorScheme
|
||||||
const colorMap = {
|
const colorMap = {
|
||||||
S: "red",
|
S: "red",
|
||||||
A: "orange",
|
A: "orange",
|
||||||
B: "yellow",
|
B: "yellow",
|
||||||
C: "green",
|
C: "green",
|
||||||
};
|
};
|
||||||
return colorMap[importance] || "gray";
|
return { colorScheme: colorMap[importance] || "gray" };
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理搜索输入
|
// 处理搜索输入
|
||||||
@@ -129,19 +196,26 @@ const NewsEventsTab = ({
|
|||||||
// 如果开始页大于1,显示省略号
|
// 如果开始页大于1,显示省略号
|
||||||
if (startPage > 1) {
|
if (startPage > 1) {
|
||||||
pageButtons.push(
|
pageButtons.push(
|
||||||
<Text key="start-ellipsis" fontSize="sm" color="gray.400">
|
<Text key="start-ellipsis" fontSize="sm" color={theme.textMuted}>
|
||||||
...
|
...
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = startPage; i <= endPage; i++) {
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
const isActive = i === currentPage;
|
||||||
pageButtons.push(
|
pageButtons.push(
|
||||||
<Button
|
<Button
|
||||||
key={i}
|
key={i}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant={i === currentPage ? "solid" : "outline"}
|
bg={isActive ? theme.buttonBg : (isBlackGold ? theme.inputBg : undefined)}
|
||||||
colorScheme={i === currentPage ? "blue" : "gray"}
|
color={isActive ? theme.buttonText : theme.textSecondary}
|
||||||
|
borderColor={isActive ? theme.gold : theme.cardBorder}
|
||||||
|
borderWidth="1px"
|
||||||
|
_hover={{
|
||||||
|
bg: isActive ? theme.buttonHoverBg : theme.cardHoverBg,
|
||||||
|
borderColor: theme.gold
|
||||||
|
}}
|
||||||
onClick={() => handlePageChange(i)}
|
onClick={() => handlePageChange(i)}
|
||||||
isDisabled={newsLoading}
|
isDisabled={newsLoading}
|
||||||
>
|
>
|
||||||
@@ -153,7 +227,7 @@ const NewsEventsTab = ({
|
|||||||
// 如果结束页小于总页数,显示省略号
|
// 如果结束页小于总页数,显示省略号
|
||||||
if (endPage < totalPages) {
|
if (endPage < totalPages) {
|
||||||
pageButtons.push(
|
pageButtons.push(
|
||||||
<Text key="end-ellipsis" fontSize="sm" color="gray.400">
|
<Text key="end-ellipsis" fontSize="sm" color={theme.textMuted}>
|
||||||
...
|
...
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
@@ -164,7 +238,7 @@ const NewsEventsTab = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack spacing={4} align="stretch">
|
<VStack spacing={4} align="stretch">
|
||||||
<Card bg={cardBg} shadow="md">
|
<Card bg={cardBg || theme.cardBg} shadow="md" borderColor={theme.cardBorder} borderWidth={isBlackGold ? "1px" : "0"}>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<VStack spacing={4} align="stretch">
|
<VStack spacing={4} align="stretch">
|
||||||
{/* 搜索框和统计信息 */}
|
{/* 搜索框和统计信息 */}
|
||||||
@@ -172,17 +246,25 @@ const NewsEventsTab = ({
|
|||||||
<HStack flex={1} minW="300px">
|
<HStack flex={1} minW="300px">
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputLeftElement pointerEvents="none">
|
<InputLeftElement pointerEvents="none">
|
||||||
<SearchIcon color="gray.400" />
|
<SearchIcon color={theme.textMuted} />
|
||||||
</InputLeftElement>
|
</InputLeftElement>
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索相关新闻..."
|
placeholder="搜索相关新闻..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
onKeyPress={handleKeyPress}
|
onKeyPress={handleKeyPress}
|
||||||
|
bg={theme.inputBg}
|
||||||
|
borderColor={theme.inputBorder}
|
||||||
|
color={theme.textPrimary}
|
||||||
|
_placeholder={{ color: theme.textMuted }}
|
||||||
|
_hover={{ borderColor: theme.gold }}
|
||||||
|
_focus={{ borderColor: theme.gold, boxShadow: `0 0 0 1px ${theme.gold}` }}
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<Button
|
<Button
|
||||||
colorScheme="blue"
|
bg={theme.buttonBg}
|
||||||
|
color={theme.buttonText}
|
||||||
|
_hover={{ bg: theme.buttonHoverBg }}
|
||||||
onClick={handleSearchSubmit}
|
onClick={handleSearchSubmit}
|
||||||
isLoading={newsLoading}
|
isLoading={newsLoading}
|
||||||
minW="80px"
|
minW="80px"
|
||||||
@@ -193,10 +275,10 @@ const NewsEventsTab = ({
|
|||||||
|
|
||||||
{newsPagination.total > 0 && (
|
{newsPagination.total > 0 && (
|
||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
<Icon as={FaNewspaper} color="blue.500" />
|
<Icon as={FaNewspaper} color={theme.gold} />
|
||||||
<Text fontSize="sm" color="gray.600">
|
<Text fontSize="sm" color={theme.textSecondary}>
|
||||||
共找到{" "}
|
共找到{" "}
|
||||||
<Text as="span" fontWeight="bold" color="blue.600">
|
<Text as="span" fontWeight="bold" color={theme.gold}>
|
||||||
{newsPagination.total}
|
{newsPagination.total}
|
||||||
</Text>{" "}
|
</Text>{" "}
|
||||||
条新闻
|
条新闻
|
||||||
@@ -211,15 +293,15 @@ const NewsEventsTab = ({
|
|||||||
{newsLoading ? (
|
{newsLoading ? (
|
||||||
<Center h="400px">
|
<Center h="400px">
|
||||||
<VStack spacing={3}>
|
<VStack spacing={3}>
|
||||||
<Spinner size="xl" color="blue.500" thickness="4px" />
|
<Spinner size="xl" color={theme.spinnerColor} thickness="4px" />
|
||||||
<Text color="gray.600">正在加载新闻...</Text>
|
<Text color={theme.textSecondary}>正在加载新闻...</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
</Center>
|
||||||
) : newsEvents.length > 0 ? (
|
) : newsEvents.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<VStack spacing={3} align="stretch">
|
<VStack spacing={3} align="stretch">
|
||||||
{newsEvents.map((event, idx) => {
|
{newsEvents.map((event, idx) => {
|
||||||
const importanceColor = getImportanceColor(
|
const importanceBadgeStyle = getImportanceBadgeStyle(
|
||||||
event.importance
|
event.importance
|
||||||
);
|
);
|
||||||
const eventTypeIcon = getEventTypeIcon(event.event_type);
|
const eventTypeIcon = getEventTypeIcon(event.event_type);
|
||||||
@@ -228,10 +310,12 @@ const NewsEventsTab = ({
|
|||||||
<Card
|
<Card
|
||||||
key={event.id || idx}
|
key={event.id || idx}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
bg={theme.cardBg}
|
||||||
|
borderColor={theme.cardBorder}
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: "gray.50",
|
bg: theme.cardHoverBg,
|
||||||
shadow: "md",
|
shadow: "md",
|
||||||
borderColor: "blue.300",
|
borderColor: theme.cardHoverBorder,
|
||||||
}}
|
}}
|
||||||
transition="all 0.2s"
|
transition="all 0.2s"
|
||||||
>
|
>
|
||||||
@@ -243,13 +327,14 @@ const NewsEventsTab = ({
|
|||||||
<HStack>
|
<HStack>
|
||||||
<Icon
|
<Icon
|
||||||
as={eventTypeIcon}
|
as={eventTypeIcon}
|
||||||
color="blue.500"
|
color={theme.gold}
|
||||||
boxSize={5}
|
boxSize={5}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
fontSize="lg"
|
fontSize="lg"
|
||||||
lineHeight="1.3"
|
lineHeight="1.3"
|
||||||
|
color={theme.textPrimary}
|
||||||
>
|
>
|
||||||
{event.title}
|
{event.title}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -259,22 +344,29 @@ const NewsEventsTab = ({
|
|||||||
<HStack spacing={2} flexWrap="wrap">
|
<HStack spacing={2} flexWrap="wrap">
|
||||||
{event.importance && (
|
{event.importance && (
|
||||||
<Badge
|
<Badge
|
||||||
colorScheme={importanceColor}
|
{...(isBlackGold ? {} : { colorScheme: importanceBadgeStyle.colorScheme, variant: "solid" })}
|
||||||
variant="solid"
|
bg={isBlackGold ? importanceBadgeStyle.bg : undefined}
|
||||||
|
color={isBlackGold ? importanceBadgeStyle.color : undefined}
|
||||||
px={2}
|
px={2}
|
||||||
>
|
>
|
||||||
{event.importance}级
|
{event.importance}级
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{event.event_type && (
|
{event.event_type && (
|
||||||
<Badge colorScheme="blue" variant="outline">
|
<Badge
|
||||||
|
{...(isBlackGold ? {} : { colorScheme: "blue", variant: "outline" })}
|
||||||
|
bg={isBlackGold ? "rgba(59, 130, 246, 0.2)" : undefined}
|
||||||
|
color={isBlackGold ? "#60A5FA" : undefined}
|
||||||
|
borderColor={isBlackGold ? "rgba(59, 130, 246, 0.3)" : undefined}
|
||||||
|
>
|
||||||
{event.event_type}
|
{event.event_type}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{event.invest_score && (
|
{event.invest_score && (
|
||||||
<Badge
|
<Badge
|
||||||
colorScheme="purple"
|
{...(isBlackGold ? {} : { colorScheme: "purple", variant: "subtle" })}
|
||||||
variant="subtle"
|
bg={isBlackGold ? "rgba(139, 92, 246, 0.2)" : undefined}
|
||||||
|
color={isBlackGold ? "#A78BFA" : undefined}
|
||||||
>
|
>
|
||||||
投资分: {event.invest_score}
|
投资分: {event.invest_score}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -287,8 +379,9 @@ const NewsEventsTab = ({
|
|||||||
<Tag
|
<Tag
|
||||||
key={kidx}
|
key={kidx}
|
||||||
size="sm"
|
size="sm"
|
||||||
colorScheme="cyan"
|
{...(isBlackGold ? {} : { colorScheme: "cyan", variant: "subtle" })}
|
||||||
variant="subtle"
|
bg={isBlackGold ? theme.tagBg : undefined}
|
||||||
|
color={isBlackGold ? theme.tagColor : undefined}
|
||||||
>
|
>
|
||||||
{typeof keyword === "string"
|
{typeof keyword === "string"
|
||||||
? keyword
|
? keyword
|
||||||
@@ -304,7 +397,7 @@ const NewsEventsTab = ({
|
|||||||
|
|
||||||
{/* 右侧信息栏 */}
|
{/* 右侧信息栏 */}
|
||||||
<VStack align="end" spacing={1} minW="100px">
|
<VStack align="end" spacing={1} minW="100px">
|
||||||
<Text fontSize="xs" color="gray.500">
|
<Text fontSize="xs" color={theme.textMuted}>
|
||||||
{event.created_at
|
{event.created_at
|
||||||
? new Date(
|
? new Date(
|
||||||
event.created_at
|
event.created_at
|
||||||
@@ -321,9 +414,9 @@ const NewsEventsTab = ({
|
|||||||
<Icon
|
<Icon
|
||||||
as={FaEye}
|
as={FaEye}
|
||||||
boxSize={3}
|
boxSize={3}
|
||||||
color="gray.400"
|
color={theme.textMuted}
|
||||||
/>
|
/>
|
||||||
<Text fontSize="xs" color="gray.500">
|
<Text fontSize="xs" color={theme.textMuted}>
|
||||||
{event.view_count}
|
{event.view_count}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
@@ -333,16 +426,16 @@ const NewsEventsTab = ({
|
|||||||
<Icon
|
<Icon
|
||||||
as={FaFire}
|
as={FaFire}
|
||||||
boxSize={3}
|
boxSize={3}
|
||||||
color="orange.400"
|
color={theme.goldLight}
|
||||||
/>
|
/>
|
||||||
<Text fontSize="xs" color="gray.500">
|
<Text fontSize="xs" color={theme.textMuted}>
|
||||||
{event.hot_score.toFixed(1)}
|
{event.hot_score.toFixed(1)}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
{event.creator && (
|
{event.creator && (
|
||||||
<Text fontSize="xs" color="gray.400">
|
<Text fontSize="xs" color={theme.textMuted}>
|
||||||
@{event.creator.username}
|
@{event.creator.username}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -353,7 +446,7 @@ const NewsEventsTab = ({
|
|||||||
{event.description && (
|
{event.description && (
|
||||||
<Text
|
<Text
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
color="gray.700"
|
color={theme.textSecondary}
|
||||||
lineHeight="1.6"
|
lineHeight="1.6"
|
||||||
>
|
>
|
||||||
{event.description}
|
{event.description}
|
||||||
@@ -367,18 +460,18 @@ const NewsEventsTab = ({
|
|||||||
<Box
|
<Box
|
||||||
pt={2}
|
pt={2}
|
||||||
borderTop="1px"
|
borderTop="1px"
|
||||||
borderColor="gray.200"
|
borderColor={theme.cardBorder}
|
||||||
>
|
>
|
||||||
<HStack spacing={6} flexWrap="wrap">
|
<HStack spacing={6} flexWrap="wrap">
|
||||||
<HStack spacing={1}>
|
<HStack spacing={1}>
|
||||||
<Icon
|
<Icon
|
||||||
as={FaChartLine}
|
as={FaChartLine}
|
||||||
boxSize={3}
|
boxSize={3}
|
||||||
color="gray.500"
|
color={theme.textMuted}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
fontSize="xs"
|
fontSize="xs"
|
||||||
color="gray.500"
|
color={theme.textMuted}
|
||||||
fontWeight="medium"
|
fontWeight="medium"
|
||||||
>
|
>
|
||||||
相关涨跌:
|
相关涨跌:
|
||||||
@@ -387,7 +480,7 @@ const NewsEventsTab = ({
|
|||||||
{event.related_avg_chg !== null &&
|
{event.related_avg_chg !== null &&
|
||||||
event.related_avg_chg !== undefined && (
|
event.related_avg_chg !== undefined && (
|
||||||
<HStack spacing={1}>
|
<HStack spacing={1}>
|
||||||
<Text fontSize="xs" color="gray.500">
|
<Text fontSize="xs" color={theme.textMuted}>
|
||||||
平均
|
平均
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
@@ -395,8 +488,8 @@ const NewsEventsTab = ({
|
|||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color={
|
color={
|
||||||
event.related_avg_chg > 0
|
event.related_avg_chg > 0
|
||||||
? "red.500"
|
? "#EF4444"
|
||||||
: "green.500"
|
: "#10B981"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{event.related_avg_chg > 0 ? "+" : ""}
|
{event.related_avg_chg > 0 ? "+" : ""}
|
||||||
@@ -407,7 +500,7 @@ const NewsEventsTab = ({
|
|||||||
{event.related_max_chg !== null &&
|
{event.related_max_chg !== null &&
|
||||||
event.related_max_chg !== undefined && (
|
event.related_max_chg !== undefined && (
|
||||||
<HStack spacing={1}>
|
<HStack spacing={1}>
|
||||||
<Text fontSize="xs" color="gray.500">
|
<Text fontSize="xs" color={theme.textMuted}>
|
||||||
最大
|
最大
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
@@ -415,8 +508,8 @@ const NewsEventsTab = ({
|
|||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color={
|
color={
|
||||||
event.related_max_chg > 0
|
event.related_max_chg > 0
|
||||||
? "red.500"
|
? "#EF4444"
|
||||||
: "green.500"
|
: "#10B981"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{event.related_max_chg > 0 ? "+" : ""}
|
{event.related_max_chg > 0 ? "+" : ""}
|
||||||
@@ -427,7 +520,7 @@ const NewsEventsTab = ({
|
|||||||
{event.related_week_chg !== null &&
|
{event.related_week_chg !== null &&
|
||||||
event.related_week_chg !== undefined && (
|
event.related_week_chg !== undefined && (
|
||||||
<HStack spacing={1}>
|
<HStack spacing={1}>
|
||||||
<Text fontSize="xs" color="gray.500">
|
<Text fontSize="xs" color={theme.textMuted}>
|
||||||
周
|
周
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
@@ -435,8 +528,8 @@ const NewsEventsTab = ({
|
|||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color={
|
color={
|
||||||
event.related_week_chg > 0
|
event.related_week_chg > 0
|
||||||
? "red.500"
|
? "#EF4444"
|
||||||
: "green.500"
|
: "#10B981"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{event.related_week_chg > 0
|
{event.related_week_chg > 0
|
||||||
@@ -465,7 +558,7 @@ const NewsEventsTab = ({
|
|||||||
flexWrap="wrap"
|
flexWrap="wrap"
|
||||||
>
|
>
|
||||||
{/* 分页信息 */}
|
{/* 分页信息 */}
|
||||||
<Text fontSize="sm" color="gray.600">
|
<Text fontSize="sm" color={theme.textSecondary}>
|
||||||
第 {newsPagination.page} / {newsPagination.pages} 页
|
第 {newsPagination.page} / {newsPagination.pages} 页
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
@@ -473,6 +566,11 @@ const NewsEventsTab = ({
|
|||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
bg={isBlackGold ? theme.inputBg : undefined}
|
||||||
|
color={theme.textSecondary}
|
||||||
|
borderColor={theme.cardBorder}
|
||||||
|
borderWidth="1px"
|
||||||
|
_hover={{ bg: theme.cardHoverBg, borderColor: theme.gold }}
|
||||||
onClick={() => handlePageChange(1)}
|
onClick={() => handlePageChange(1)}
|
||||||
isDisabled={!newsPagination.has_prev || newsLoading}
|
isDisabled={!newsPagination.has_prev || newsLoading}
|
||||||
leftIcon={<Icon as={FaChevronLeft} />}
|
leftIcon={<Icon as={FaChevronLeft} />}
|
||||||
@@ -481,6 +579,11 @@ const NewsEventsTab = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
bg={isBlackGold ? theme.inputBg : undefined}
|
||||||
|
color={theme.textSecondary}
|
||||||
|
borderColor={theme.cardBorder}
|
||||||
|
borderWidth="1px"
|
||||||
|
_hover={{ bg: theme.cardHoverBg, borderColor: theme.gold }}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handlePageChange(newsPagination.page - 1)
|
handlePageChange(newsPagination.page - 1)
|
||||||
}
|
}
|
||||||
@@ -494,6 +597,11 @@ const NewsEventsTab = ({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
bg={isBlackGold ? theme.inputBg : undefined}
|
||||||
|
color={theme.textSecondary}
|
||||||
|
borderColor={theme.cardBorder}
|
||||||
|
borderWidth="1px"
|
||||||
|
_hover={{ bg: theme.cardHoverBg, borderColor: theme.gold }}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handlePageChange(newsPagination.page + 1)
|
handlePageChange(newsPagination.page + 1)
|
||||||
}
|
}
|
||||||
@@ -503,6 +611,11 @@ const NewsEventsTab = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
bg={isBlackGold ? theme.inputBg : undefined}
|
||||||
|
color={theme.textSecondary}
|
||||||
|
borderColor={theme.cardBorder}
|
||||||
|
borderWidth="1px"
|
||||||
|
_hover={{ bg: theme.cardHoverBg, borderColor: theme.gold }}
|
||||||
onClick={() => handlePageChange(newsPagination.pages)}
|
onClick={() => handlePageChange(newsPagination.pages)}
|
||||||
isDisabled={!newsPagination.has_next || newsLoading}
|
isDisabled={!newsPagination.has_next || newsLoading}
|
||||||
rightIcon={<Icon as={FaChevronRight} />}
|
rightIcon={<Icon as={FaChevronRight} />}
|
||||||
@@ -517,11 +630,11 @@ const NewsEventsTab = ({
|
|||||||
) : (
|
) : (
|
||||||
<Center h="400px">
|
<Center h="400px">
|
||||||
<VStack spacing={3}>
|
<VStack spacing={3}>
|
||||||
<Icon as={FaNewspaper} boxSize={16} color="gray.300" />
|
<Icon as={FaNewspaper} boxSize={16} color={isBlackGold ? theme.gold : "gray.300"} opacity={0.5} />
|
||||||
<Text color="gray.500" fontSize="lg" fontWeight="medium">
|
<Text color={theme.textSecondary} fontSize="lg" fontWeight="medium">
|
||||||
暂无相关新闻
|
暂无相关新闻
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontSize="sm" color="gray.400">
|
<Text fontSize="sm" color={theme.textMuted}>
|
||||||
{searchQuery ? "尝试修改搜索关键词" : "该公司暂无新闻动态"}
|
{searchQuery ? "尝试修改搜索关键词" : "该公司暂无新闻动态"}
|
||||||
</Text>
|
</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
@@ -1,23 +1,49 @@
|
|||||||
// src/views/Company/components/DynamicTracking/components/ForecastPanel.js
|
// src/views/Company/components/DynamicTracking/components/ForecastPanel.js
|
||||||
// 业绩预告面板
|
// 业绩预告面板 - 黑金主题
|
||||||
|
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
VStack,
|
VStack,
|
||||||
Card,
|
Box,
|
||||||
CardBody,
|
Flex,
|
||||||
HStack,
|
|
||||||
Badge,
|
|
||||||
Text,
|
Text,
|
||||||
Spinner,
|
Spinner,
|
||||||
Center,
|
Center,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { Tag } from 'antd';
|
||||||
import { logger } from '@utils/logger';
|
import { logger } from '@utils/logger';
|
||||||
import { getApiBase } from '@utils/apiConfig';
|
import { getApiBase } from '@utils/apiConfig';
|
||||||
import { THEME } from '../../CompanyOverview/BasicInfoTab/config';
|
|
||||||
|
|
||||||
const API_BASE_URL = getApiBase();
|
const API_BASE_URL = getApiBase();
|
||||||
|
|
||||||
|
// 黑金主题
|
||||||
|
const THEME = {
|
||||||
|
gold: '#D4AF37',
|
||||||
|
goldLight: 'rgba(212, 175, 55, 0.15)',
|
||||||
|
goldBorder: 'rgba(212, 175, 55, 0.3)',
|
||||||
|
bgDark: '#1A202C',
|
||||||
|
cardBg: 'rgba(26, 32, 44, 0.6)',
|
||||||
|
text: '#E2E8F0',
|
||||||
|
textSecondary: '#A0AEC0',
|
||||||
|
positive: '#E53E3E',
|
||||||
|
negative: '#48BB78',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 预告类型配色
|
||||||
|
const getForecastTypeStyle = (type) => {
|
||||||
|
const styles = {
|
||||||
|
'预增': { color: '#E53E3E', bg: 'rgba(229, 62, 62, 0.15)', border: 'rgba(229, 62, 62, 0.3)' },
|
||||||
|
'预减': { color: '#48BB78', bg: 'rgba(72, 187, 120, 0.15)', border: 'rgba(72, 187, 120, 0.3)' },
|
||||||
|
'扭亏': { color: '#D4AF37', bg: 'rgba(212, 175, 55, 0.15)', border: 'rgba(212, 175, 55, 0.3)' },
|
||||||
|
'首亏': { color: '#48BB78', bg: 'rgba(72, 187, 120, 0.15)', border: 'rgba(72, 187, 120, 0.3)' },
|
||||||
|
'续亏': { color: '#718096', bg: 'rgba(113, 128, 150, 0.15)', border: 'rgba(113, 128, 150, 0.3)' },
|
||||||
|
'续盈': { color: '#E53E3E', bg: 'rgba(229, 62, 62, 0.15)', border: 'rgba(229, 62, 62, 0.3)' },
|
||||||
|
'略增': { color: '#ED8936', bg: 'rgba(237, 137, 54, 0.15)', border: 'rgba(237, 137, 54, 0.3)' },
|
||||||
|
'略减': { color: '#38B2AC', bg: 'rgba(56, 178, 172, 0.15)', border: 'rgba(56, 178, 172, 0.3)' },
|
||||||
|
};
|
||||||
|
return styles[type] || { color: THEME.gold, bg: THEME.goldLight, border: THEME.goldBorder };
|
||||||
|
};
|
||||||
|
|
||||||
const ForecastPanel = ({ stockCode }) => {
|
const ForecastPanel = ({ stockCode }) => {
|
||||||
const [forecast, setForecast] = useState(null);
|
const [forecast, setForecast] = useState(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -63,33 +89,69 @@ const ForecastPanel = ({ stockCode }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack spacing={4} align="stretch">
|
<VStack spacing={3} align="stretch">
|
||||||
{forecast.forecasts.map((item, idx) => (
|
{forecast.forecasts.map((item, idx) => {
|
||||||
<Card key={idx} bg={THEME.cardBg} borderColor={THEME.border} borderWidth="1px">
|
const typeStyle = getForecastTypeStyle(item.forecast_type);
|
||||||
<CardBody>
|
|
||||||
<HStack justify="space-between" mb={2}>
|
return (
|
||||||
<Badge colorScheme="blue">{item.forecast_type}</Badge>
|
<Box
|
||||||
|
key={idx}
|
||||||
|
bg={THEME.cardBg}
|
||||||
|
border="1px solid"
|
||||||
|
borderColor={THEME.goldBorder}
|
||||||
|
borderRadius="md"
|
||||||
|
p={4}
|
||||||
|
>
|
||||||
|
{/* 头部:类型标签 + 报告期 */}
|
||||||
|
<Flex justify="space-between" align="center" mb={3}>
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
color: typeStyle.color,
|
||||||
|
background: typeStyle.bg,
|
||||||
|
border: `1px solid ${typeStyle.border}`,
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.forecast_type}
|
||||||
|
</Tag>
|
||||||
<Text fontSize="sm" color={THEME.textSecondary}>
|
<Text fontSize="sm" color={THEME.textSecondary}>
|
||||||
报告期: {item.report_date}
|
报告期: {item.report_date}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</Flex>
|
||||||
<Text mb={2} color={THEME.text}>{item.content}</Text>
|
|
||||||
|
{/* 内容 */}
|
||||||
|
<Text color={THEME.text} fontSize="sm" lineHeight="1.6" mb={3}>
|
||||||
|
{item.content}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* 原因(如有) */}
|
||||||
{item.reason && (
|
{item.reason && (
|
||||||
<Text fontSize="sm" color={THEME.textSecondary}>
|
<Text fontSize="xs" color={THEME.textSecondary} mb={3}>
|
||||||
{item.reason}
|
{item.reason}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{item.change_range?.lower && (
|
|
||||||
<HStack mt={2}>
|
{/* 变动范围 */}
|
||||||
<Text fontSize="sm" color={THEME.textSecondary}>预计变动范围:</Text>
|
{item.change_range?.lower !== undefined && (
|
||||||
<Badge colorScheme="green">
|
<Flex align="center" gap={2}>
|
||||||
|
<Text fontSize="sm" color={THEME.textSecondary}>
|
||||||
|
预计变动范围:
|
||||||
|
</Text>
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
color: THEME.gold,
|
||||||
|
background: THEME.goldLight,
|
||||||
|
border: `1px solid ${THEME.goldBorder}`,
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{item.change_range.lower}% ~ {item.change_range.upper}%
|
{item.change_range.lower}% ~ {item.change_range.upper}%
|
||||||
</Badge>
|
</Tag>
|
||||||
</HStack>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</CardBody>
|
</Box>
|
||||||
</Card>
|
);
|
||||||
))}
|
})}
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ const NewsPanel = ({ stockCode }) => {
|
|||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
cardBg="white"
|
themePreset="blackGold"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user