diff --git a/src/views/Company/components/ConceptSector/index.js b/src/views/Company/components/ConceptSector/index.js
index bc395109..d81229e2 100644
--- a/src/views/Company/components/ConceptSector/index.js
+++ b/src/views/Company/components/ConceptSector/index.js
@@ -1,7 +1,7 @@
// src/views/Company/components/ConceptSector/index.js
-// 个股详情页 - 概念板块 Tab 组件
+// 个股详情页 - 概念板块 Tab 组件(黑金主题)
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState, useEffect, useCallback, memo } from 'react';
import {
Box,
VStack,
@@ -13,8 +13,6 @@ import {
CardBody,
Skeleton,
SkeletonText,
- Alert,
- AlertIcon,
Divider,
Button,
Tooltip,
@@ -22,10 +20,11 @@ import {
TagLabel,
Wrap,
WrapItem,
- useColorModeValue,
Icon,
Flex,
Spacer,
+ Spinner,
+ Center,
} from '@chakra-ui/react';
import {
Layers,
@@ -35,12 +34,42 @@ import {
Calendar,
Hash,
Sparkles,
+ AlertCircle,
+ RefreshCw,
} from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { logger } from '@utils/logger';
import { getApiBase } from '@utils/apiConfig';
import { getConceptHtmlUrl } from '@utils/textUtils';
+// ============================================
+// 黑金主题配置(与其他 Tab 保持一致)
+// ============================================
+const THEME = {
+ bg: '#0A0E17',
+ cardBg: '#1A1F2E',
+ cardHoverBg: '#212633',
+ cardBorder: 'rgba(212, 175, 55, 0.2)',
+ cardHoverBorder: '#F4D03F',
+ textPrimary: '#E8E9ED',
+ textSecondary: '#A0A4B8',
+ textMuted: '#6B7280',
+ gold: '#F4D03F',
+ goldLight: '#FFD54F',
+ // 涨跌色
+ positive: '#EF4444',
+ negativE: '#22C55E',
+ positiveBg: 'rgba(239, 68, 68, 0.15)',
+ negativeBg: 'rgba(34, 197, 94, 0.15)',
+ // 标签色
+ tagBg: 'rgba(212, 175, 55, 0.15)',
+ tagColor: '#F4D03F',
+ // 按钮
+ buttonBg: '#D4AF37',
+ buttonText: '#0A0E17',
+ buttonHoverBg: '#FFD54F',
+};
+
// API 配置
const API_BASE_URL =
process.env.NODE_ENV === 'production'
@@ -48,182 +77,234 @@ const API_BASE_URL =
: 'http://111.198.58.126:16801';
/**
- * 单个概念卡片组件
+ * 单个概念卡片组件(黑金主题)
*/
-const ConceptCard = ({ concept, onClick }) => {
- const cardBg = useColorModeValue('white', 'rgba(26, 32, 44, 0.8)');
- const borderColor = useColorModeValue('gray.200', 'rgba(255, 195, 0, 0.2)');
- const textSecondary = useColorModeValue('gray.600', 'gray.400');
-
- // 涨跌幅颜色
+const ConceptCard = memo(({ concept, onClick }) => {
+ // 涨跌幅
const changePct = concept.price_info?.avg_change_pct;
const hasChange = changePct !== null && changePct !== undefined;
const isPositive = changePct > 0;
- const changeColor = isPositive ? 'red.500' : changePct < 0 ? 'green.500' : 'gray.500';
- const changeBgColor = isPositive
- ? 'rgba(239, 68, 68, 0.1)'
- : changePct < 0
- ? 'rgba(34, 197, 94, 0.1)'
- : 'rgba(128, 128, 128, 0.1)';
+ const isNegative = changePct < 0;
- // 层级信息
+ // 层级路径
const hierarchy = concept.hierarchy;
const hierarchyPath = hierarchy
? [hierarchy.lv1, hierarchy.lv2, hierarchy.lv3].filter(Boolean).join(' > ')
: null;
return (
-
-
-
- {/* 头部:概念名称 + 涨跌幅 */}
-
-
-
-
-
- {concept.concept}
-
-
- {hierarchyPath && (
-
- {hierarchyPath}
-
- )}
-
-
- {hasChange && (
-
+ {/* 头部:概念名称 + 涨跌幅 */}
+
+
+
+
+
-
-
-
- {isPositive ? '+' : ''}
- {changePct.toFixed(2)}%
-
-
-
- )}
-
-
- {/* 描述 */}
- {concept.description && (
-
- {concept.description}
-
- )}
-
- {/* 标签 */}
- {concept.tags && concept.tags.length > 0 && (
-
- {concept.tags.slice(0, 4).map((tag, idx) => (
-
-
- {tag}
-
-
- ))}
- {concept.tags.length > 4 && (
-
-
- +{concept.tags.length - 4}
-
-
- )}
-
- )}
-
-
-
- {/* 底部信息 */}
-
-
-
- {concept.stock_count || 0} 只成分股
+ {concept.concept}
+
- {concept.price_info?.trade_date && (
-
-
- {concept.price_info.trade_date}
-
+ {hierarchyPath && (
+
+ {hierarchyPath}
+
)}
+
+
+ {hasChange && (
+
+
+
+
+ {isPositive ? '+' : ''}
+ {changePct.toFixed(2)}%
+
+
+
+ )}
+
+
+ {/* 描述 */}
+ {concept.description && (
+
+ {concept.description}
+
+ )}
+
+ {/* 标签 */}
+ {concept.tags && concept.tags.length > 0 && (
+
+ {concept.tags.slice(0, 4).map((tag, idx) => (
+
+
+ {tag}
+
+
+ ))}
+ {concept.tags.length > 4 && (
+
+
+ +{concept.tags.length - 4}
+
+
+ )}
+
+ )}
+
+
+
+ {/* 底部信息 */}
+
+
+
+ {concept.stock_count || 0} 只成分股
-
-
-
+ {concept.price_info?.trade_date && (
+
+
+ {concept.price_info.trade_date}
+
+ )}
+
+
+
);
-};
+});
+
+ConceptCard.displayName = 'ConceptCard';
/**
- * 骨架屏加载组件
+ * 骨架屏加载组件(黑金主题)
*/
const ConceptSkeleton = () => (
{[...Array(6)].map((_, i) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
))}
);
/**
- * 概念板块组件
+ * 空状态组件
+ */
+const EmptyState = () => (
+
+
+
+
+ 暂无关联概念数据
+
+
+
+);
+
+/**
+ * 错误状态组件
+ */
+const ErrorState = ({ onRetry }) => (
+
+
+
+
+ 获取概念数据失败
+
+ }
+ onClick={onRetry}
+ >
+ 重试
+
+
+
+);
+
+/**
+ * 概念板块组件(黑金主题)
* @param {Object} props
* @param {string} props.stockCode - 股票代码
*/
-const ConceptSector = ({ stockCode }) => {
+const ConceptSector = memo(({ stockCode }) => {
const navigate = useNavigate();
const [concepts, setConcepts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- const bgColor = useColorModeValue('gray.50', 'transparent');
- const headerBg = useColorModeValue('white', 'rgba(26, 32, 44, 0.6)');
- const textColor = useColorModeValue('gray.800', 'white');
- const textSecondary = useColorModeValue('gray.600', 'gray.400');
-
/**
* 获取股票关联的概念
*/
@@ -244,7 +325,7 @@ const ConceptSector = ({ stockCode }) => {
setConcepts(data.concepts || []);
} catch (err) {
logger.error('ConceptSector', 'fetchStockConcepts', err, { stockCode });
- setError('获取概念数据失败,请稍后重试');
+ setError('获取概念数据失败');
} finally {
setLoading(false);
}
@@ -257,14 +338,10 @@ const ConceptSector = ({ stockCode }) => {
/**
* 点击概念跳转
*/
- const handleConceptClick = useCallback(
- (concept) => {
- // 跳转到概念详情页(外部链接)
- const url = getConceptHtmlUrl(concept.concept);
- window.open(url, '_blank');
- },
- []
- );
+ const handleConceptClick = useCallback((concept) => {
+ const url = getConceptHtmlUrl(concept.concept);
+ window.open(url, '_blank');
+ }, []);
/**
* 跳转到概念中心
@@ -273,7 +350,7 @@ const ConceptSector = ({ stockCode }) => {
navigate('/concept');
}, [navigate]);
- // 按涨跌幅排序概念
+ // 按涨跌幅排序
const sortedConcepts = [...concepts].sort((a, b) => {
const aChange = a.price_info?.avg_change_pct ?? -Infinity;
const bChange = b.price_info?.avg_change_pct ?? -Infinity;
@@ -282,48 +359,53 @@ const ConceptSector = ({ stockCode }) => {
// 统计信息
const totalConcepts = concepts.length;
- const positiveConcepts = concepts.filter(
- (c) => c.price_info?.avg_change_pct > 0
- ).length;
- const negativeConcepts = concepts.filter(
- (c) => c.price_info?.avg_change_pct < 0
- ).length;
+ const positiveConcepts = concepts.filter((c) => c.price_info?.avg_change_pct > 0).length;
+ const negativeConcepts = concepts.filter((c) => c.price_info?.avg_change_pct < 0).length;
return (
-
- {/* 头部统计信息 */}
-
-
-
+
+
+
+ {/* 头部统计信息 */}
+
-
-
+
+
概念板块
-
+
共
-
+
{totalConcepts}
-
+
个概念
-
+
+
+
-
-
+
+
{positiveConcepts}
+
-
-
+
+
{negativeConcepts}
@@ -331,11 +413,12 @@ const ConceptSector = ({ stockCode }) => {
-
+
}
onClick={handleGoToConceptCenter}
>
@@ -343,38 +426,31 @@ const ConceptSector = ({ stockCode }) => {
-
-
- {/* 内容区域 */}
- {loading ? (
-
- ) : error ? (
-
-
- {error}
-
-
- ) : concepts.length === 0 ? (
-
-
- 暂无关联概念数据
-
- ) : (
-
- {sortedConcepts.map((concept, index) => (
- handleConceptClick(concept)}
- />
- ))}
-
- )}
-
+ {/* 内容区域 */}
+ {loading ? (
+
+ ) : error ? (
+
+ ) : concepts.length === 0 ? (
+
+ ) : (
+
+ {sortedConcepts.map((concept, index) => (
+ handleConceptClick(concept)}
+ />
+ ))}
+
+ )}
+
+
+
);
-};
+});
+
+ConceptSector.displayName = 'ConceptSector';
export default ConceptSector;