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, 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>

View File

@@ -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>
); );
}; };

View File

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