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:
zdl
2025-12-16 20:37:29 +08:00
parent 515b538c84
commit 2d03c88f43
3 changed files with 249 additions and 74 deletions

View File

@@ -36,6 +36,58 @@ import {
FaChevronRight,
} 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 组件
*
@@ -48,6 +100,7 @@ import {
* - onSearch: 搜索提交回调 () => void
* - onPageChange: 分页回调 (page) => void
* - cardBg: 卡片背景色
* - themePreset: 主题预设 'blackGold' | 'default'
*/
const NewsEventsTab = ({
newsEvents = [],
@@ -65,7 +118,11 @@ const NewsEventsTab = ({
onSearch,
onPageChange,
cardBg,
themePreset = "default",
}) => {
// 获取主题配色
const theme = THEME_PRESETS[themePreset] || THEME_PRESETS.default;
const isBlackGold = themePreset === "blackGold";
// 事件类型图标映射
const getEventTypeIcon = (eventType) => {
const iconMap = {
@@ -80,15 +137,25 @@ const NewsEventsTab = ({
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 = {
S: "red",
A: "orange",
B: "yellow",
C: "green",
};
return colorMap[importance] || "gray";
return { colorScheme: colorMap[importance] || "gray" };
};
// 处理搜索输入
@@ -129,19 +196,26 @@ const NewsEventsTab = ({
// 如果开始页大于1显示省略号
if (startPage > 1) {
pageButtons.push(
<Text key="start-ellipsis" fontSize="sm" color="gray.400">
<Text key="start-ellipsis" fontSize="sm" color={theme.textMuted}>
...
</Text>
);
}
for (let i = startPage; i <= endPage; i++) {
const isActive = i === currentPage;
pageButtons.push(
<Button
key={i}
size="sm"
variant={i === currentPage ? "solid" : "outline"}
colorScheme={i === currentPage ? "blue" : "gray"}
bg={isActive ? theme.buttonBg : (isBlackGold ? theme.inputBg : undefined)}
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)}
isDisabled={newsLoading}
>
@@ -153,7 +227,7 @@ const NewsEventsTab = ({
// 如果结束页小于总页数,显示省略号
if (endPage < totalPages) {
pageButtons.push(
<Text key="end-ellipsis" fontSize="sm" color="gray.400">
<Text key="end-ellipsis" fontSize="sm" color={theme.textMuted}>
...
</Text>
);
@@ -164,7 +238,7 @@ const NewsEventsTab = ({
return (
<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>
<VStack spacing={4} align="stretch">
{/* 搜索框和统计信息 */}
@@ -172,17 +246,25 @@ const NewsEventsTab = ({
<HStack flex={1} minW="300px">
<InputGroup>
<InputLeftElement pointerEvents="none">
<SearchIcon color="gray.400" />
<SearchIcon color={theme.textMuted} />
</InputLeftElement>
<Input
placeholder="搜索相关新闻..."
value={searchQuery}
onChange={handleInputChange}
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>
<Button
colorScheme="blue"
bg={theme.buttonBg}
color={theme.buttonText}
_hover={{ bg: theme.buttonHoverBg }}
onClick={handleSearchSubmit}
isLoading={newsLoading}
minW="80px"
@@ -193,10 +275,10 @@ const NewsEventsTab = ({
{newsPagination.total > 0 && (
<HStack spacing={2}>
<Icon as={FaNewspaper} color="blue.500" />
<Text fontSize="sm" color="gray.600">
<Icon as={FaNewspaper} color={theme.gold} />
<Text fontSize="sm" color={theme.textSecondary}>
共找到{" "}
<Text as="span" fontWeight="bold" color="blue.600">
<Text as="span" fontWeight="bold" color={theme.gold}>
{newsPagination.total}
</Text>{" "}
条新闻
@@ -211,15 +293,15 @@ const NewsEventsTab = ({
{newsLoading ? (
<Center h="400px">
<VStack spacing={3}>
<Spinner size="xl" color="blue.500" thickness="4px" />
<Text color="gray.600">正在加载新闻...</Text>
<Spinner size="xl" color={theme.spinnerColor} thickness="4px" />
<Text color={theme.textSecondary}>正在加载新闻...</Text>
</VStack>
</Center>
) : newsEvents.length > 0 ? (
<>
<VStack spacing={3} align="stretch">
{newsEvents.map((event, idx) => {
const importanceColor = getImportanceColor(
const importanceBadgeStyle = getImportanceBadgeStyle(
event.importance
);
const eventTypeIcon = getEventTypeIcon(event.event_type);
@@ -228,10 +310,12 @@ const NewsEventsTab = ({
<Card
key={event.id || idx}
variant="outline"
bg={theme.cardBg}
borderColor={theme.cardBorder}
_hover={{
bg: "gray.50",
bg: theme.cardHoverBg,
shadow: "md",
borderColor: "blue.300",
borderColor: theme.cardHoverBorder,
}}
transition="all 0.2s"
>
@@ -243,13 +327,14 @@ const NewsEventsTab = ({
<HStack>
<Icon
as={eventTypeIcon}
color="blue.500"
color={theme.gold}
boxSize={5}
/>
<Text
fontWeight="bold"
fontSize="lg"
lineHeight="1.3"
color={theme.textPrimary}
>
{event.title}
</Text>
@@ -259,22 +344,29 @@ const NewsEventsTab = ({
<HStack spacing={2} flexWrap="wrap">
{event.importance && (
<Badge
colorScheme={importanceColor}
variant="solid"
{...(isBlackGold ? {} : { colorScheme: importanceBadgeStyle.colorScheme, variant: "solid" })}
bg={isBlackGold ? importanceBadgeStyle.bg : undefined}
color={isBlackGold ? importanceBadgeStyle.color : undefined}
px={2}
>
{event.importance}
</Badge>
)}
{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}
</Badge>
)}
{event.invest_score && (
<Badge
colorScheme="purple"
variant="subtle"
{...(isBlackGold ? {} : { colorScheme: "purple", variant: "subtle" })}
bg={isBlackGold ? "rgba(139, 92, 246, 0.2)" : undefined}
color={isBlackGold ? "#A78BFA" : undefined}
>
投资分: {event.invest_score}
</Badge>
@@ -287,8 +379,9 @@ const NewsEventsTab = ({
<Tag
key={kidx}
size="sm"
colorScheme="cyan"
variant="subtle"
{...(isBlackGold ? {} : { colorScheme: "cyan", variant: "subtle" })}
bg={isBlackGold ? theme.tagBg : undefined}
color={isBlackGold ? theme.tagColor : undefined}
>
{typeof keyword === "string"
? keyword
@@ -304,7 +397,7 @@ const NewsEventsTab = ({
{/* 右侧信息栏 */}
<VStack align="end" spacing={1} minW="100px">
<Text fontSize="xs" color="gray.500">
<Text fontSize="xs" color={theme.textMuted}>
{event.created_at
? new Date(
event.created_at
@@ -321,9 +414,9 @@ const NewsEventsTab = ({
<Icon
as={FaEye}
boxSize={3}
color="gray.400"
color={theme.textMuted}
/>
<Text fontSize="xs" color="gray.500">
<Text fontSize="xs" color={theme.textMuted}>
{event.view_count}
</Text>
</HStack>
@@ -333,16 +426,16 @@ const NewsEventsTab = ({
<Icon
as={FaFire}
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)}
</Text>
</HStack>
)}
</HStack>
{event.creator && (
<Text fontSize="xs" color="gray.400">
<Text fontSize="xs" color={theme.textMuted}>
@{event.creator.username}
</Text>
)}
@@ -353,7 +446,7 @@ const NewsEventsTab = ({
{event.description && (
<Text
fontSize="sm"
color="gray.700"
color={theme.textSecondary}
lineHeight="1.6"
>
{event.description}
@@ -367,18 +460,18 @@ const NewsEventsTab = ({
<Box
pt={2}
borderTop="1px"
borderColor="gray.200"
borderColor={theme.cardBorder}
>
<HStack spacing={6} flexWrap="wrap">
<HStack spacing={1}>
<Icon
as={FaChartLine}
boxSize={3}
color="gray.500"
color={theme.textMuted}
/>
<Text
fontSize="xs"
color="gray.500"
color={theme.textMuted}
fontWeight="medium"
>
相关涨跌:
@@ -387,7 +480,7 @@ const NewsEventsTab = ({
{event.related_avg_chg !== null &&
event.related_avg_chg !== undefined && (
<HStack spacing={1}>
<Text fontSize="xs" color="gray.500">
<Text fontSize="xs" color={theme.textMuted}>
平均
</Text>
<Text
@@ -395,8 +488,8 @@ const NewsEventsTab = ({
fontWeight="bold"
color={
event.related_avg_chg > 0
? "red.500"
: "green.500"
? "#EF4444"
: "#10B981"
}
>
{event.related_avg_chg > 0 ? "+" : ""}
@@ -407,7 +500,7 @@ const NewsEventsTab = ({
{event.related_max_chg !== null &&
event.related_max_chg !== undefined && (
<HStack spacing={1}>
<Text fontSize="xs" color="gray.500">
<Text fontSize="xs" color={theme.textMuted}>
最大
</Text>
<Text
@@ -415,8 +508,8 @@ const NewsEventsTab = ({
fontWeight="bold"
color={
event.related_max_chg > 0
? "red.500"
: "green.500"
? "#EF4444"
: "#10B981"
}
>
{event.related_max_chg > 0 ? "+" : ""}
@@ -427,7 +520,7 @@ const NewsEventsTab = ({
{event.related_week_chg !== null &&
event.related_week_chg !== undefined && (
<HStack spacing={1}>
<Text fontSize="xs" color="gray.500">
<Text fontSize="xs" color={theme.textMuted}>
</Text>
<Text
@@ -435,8 +528,8 @@ const NewsEventsTab = ({
fontWeight="bold"
color={
event.related_week_chg > 0
? "red.500"
: "green.500"
? "#EF4444"
: "#10B981"
}
>
{event.related_week_chg > 0
@@ -465,7 +558,7 @@ const NewsEventsTab = ({
flexWrap="wrap"
>
{/* 分页信息 */}
<Text fontSize="sm" color="gray.600">
<Text fontSize="sm" color={theme.textSecondary}>
{newsPagination.page} / {newsPagination.pages}
</Text>
@@ -473,6 +566,11 @@ const NewsEventsTab = ({
<HStack spacing={2}>
<Button
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)}
isDisabled={!newsPagination.has_prev || newsLoading}
leftIcon={<Icon as={FaChevronLeft} />}
@@ -481,6 +579,11 @@ const NewsEventsTab = ({
</Button>
<Button
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.page - 1)
}
@@ -494,6 +597,11 @@ const NewsEventsTab = ({
<Button
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.page + 1)
}
@@ -503,6 +611,11 @@ const NewsEventsTab = ({
</Button>
<Button
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)}
isDisabled={!newsPagination.has_next || newsLoading}
rightIcon={<Icon as={FaChevronRight} />}
@@ -517,11 +630,11 @@ const NewsEventsTab = ({
) : (
<Center h="400px">
<VStack spacing={3}>
<Icon as={FaNewspaper} boxSize={16} color="gray.300" />
<Text color="gray.500" fontSize="lg" fontWeight="medium">
<Icon as={FaNewspaper} boxSize={16} color={isBlackGold ? theme.gold : "gray.300"} opacity={0.5} />
<Text color={theme.textSecondary} fontSize="lg" fontWeight="medium">
暂无相关新闻
</Text>
<Text fontSize="sm" color="gray.400">
<Text fontSize="sm" color={theme.textMuted}>
{searchQuery ? "尝试修改搜索关键词" : "该公司暂无新闻动态"}
</Text>
</VStack>

View File

@@ -1,23 +1,49 @@
// src/views/Company/components/DynamicTracking/components/ForecastPanel.js
// 业绩预告面板
// 业绩预告面板 - 黑金主题
import React, { useState, useEffect, useCallback } from 'react';
import {
VStack,
Card,
CardBody,
HStack,
Badge,
Box,
Flex,
Text,
Spinner,
Center,
} from '@chakra-ui/react';
import { Tag } from 'antd';
import { logger } from '@utils/logger';
import { getApiBase } from '@utils/apiConfig';
import { THEME } from '../../CompanyOverview/BasicInfoTab/config';
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 [forecast, setForecast] = useState(null);
const [loading, setLoading] = useState(false);
@@ -63,33 +89,69 @@ const ForecastPanel = ({ stockCode }) => {
}
return (
<VStack spacing={4} align="stretch">
{forecast.forecasts.map((item, idx) => (
<Card key={idx} bg={THEME.cardBg} borderColor={THEME.border} borderWidth="1px">
<CardBody>
<HStack justify="space-between" mb={2}>
<Badge colorScheme="blue">{item.forecast_type}</Badge>
<VStack spacing={3} align="stretch">
{forecast.forecasts.map((item, idx) => {
const typeStyle = getForecastTypeStyle(item.forecast_type);
return (
<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}>
报告期: {item.report_date}
</Text>
</HStack>
<Text mb={2} color={THEME.text}>{item.content}</Text>
</Flex>
{/* 内容 */}
<Text color={THEME.text} fontSize="sm" lineHeight="1.6" mb={3}>
{item.content}
</Text>
{/* 原因(如有) */}
{item.reason && (
<Text fontSize="sm" color={THEME.textSecondary}>
<Text fontSize="xs" color={THEME.textSecondary} mb={3}>
{item.reason}
</Text>
)}
{item.change_range?.lower && (
<HStack mt={2}>
<Text fontSize="sm" color={THEME.textSecondary}>预计变动范围:</Text>
<Badge colorScheme="green">
{/* 变动范围 */}
{item.change_range?.lower !== undefined && (
<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}%
</Badge>
</HStack>
</Tag>
</Flex>
)}
</CardBody>
</Card>
))}
</Box>
);
})}
</VStack>
);
};

View File

@@ -107,7 +107,7 @@ const NewsPanel = ({ stockCode }) => {
onSearchChange={handleSearchChange}
onSearch={handleSearch}
onPageChange={handlePageChange}
cardBg="white"
themePreset="blackGold"
/>
);
};