diff --git a/src/views/Concept/index.js b/src/views/Concept/index.js
index 0d0061c8..b35812dc 100644
--- a/src/views/Concept/index.js
+++ b/src/views/Concept/index.js
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { logger } from '../../utils/logger';
import { getConceptHtmlUrl } from '../../utils/textUtils';
@@ -116,6 +116,7 @@ import {
Network,
TrendingUp,
Zap,
+ RefreshCw,
} from 'lucide-react';
import { keyframes } from '@emotion/react';
import ConceptTimelineModal from './ConceptTimelineModal';
@@ -123,6 +124,7 @@ import ConceptStatsPanel from './components/ConceptStatsPanel';
import HierarchyView from './components/HierarchyView';
import ForceGraphView from './components/ForceGraphView';
import BreadcrumbNav from './components/BreadcrumbNav';
+import ChartContainer from './components/ChartContainer';
import ConceptStocksModal from '@components/ConceptStocksModal';
import TradeDatePicker from '@components/TradeDatePicker';
// 导航栏已由 MainLayout 提供,无需在此导入
@@ -367,6 +369,9 @@ const ConceptCenter = () => {
const [totalPages, setTotalPages] = useState(1);
const [viewMode, setViewMode] = useState('list'); // 默认列表视图
+ // 图表共享的钻取路径状态(tab 切换时保持)
+ const [chartDrillPath, setChartDrillPath] = useState(null);
+
// 层级筛选状态
const [hierarchyFilter, setHierarchyFilter] = useState({ lv1: null, lv2: null, lv3: null });
@@ -590,6 +595,41 @@ const ConceptCenter = () => {
fetchConcepts(searchQuery, 1, selectedDate, sortBy, filter);
}, [searchQuery, selectedDate, sortBy, updateUrlParams, fetchConcepts]);
+ // 图表面包屑导航项(从 chartDrillPath 派生)
+ const chartBreadcrumbItems = useMemo(() => {
+ const items = [{ label: '全部分类', path: null }];
+
+ if (chartDrillPath?.lv1) {
+ items.push({ label: chartDrillPath.lv1, path: { lv1: chartDrillPath.lv1 } });
+ }
+ if (chartDrillPath?.lv2) {
+ items.push({ label: chartDrillPath.lv2, path: { lv1: chartDrillPath.lv1, lv2: chartDrillPath.lv2 } });
+ }
+ if (chartDrillPath?.lv3) {
+ items.push({ label: chartDrillPath.lv3, path: { lv1: chartDrillPath.lv1, lv2: chartDrillPath.lv2, lv3: chartDrillPath.lv3 } });
+ }
+
+ return items;
+ }, [chartDrillPath]);
+
+ // 图表面包屑导航回调
+ const handleChartNavigate = useCallback((path) => {
+ setChartDrillPath(path);
+ }, []);
+
+ // 图表返回上一层
+ const handleChartGoBack = useCallback(() => {
+ if (!chartDrillPath) return;
+
+ if (chartDrillPath.lv3) {
+ setChartDrillPath({ lv1: chartDrillPath.lv1, lv2: chartDrillPath.lv2 });
+ } else if (chartDrillPath.lv2) {
+ setChartDrillPath({ lv1: chartDrillPath.lv1 });
+ } else if (chartDrillPath.lv1) {
+ setChartDrillPath(null);
+ }
+ }, [chartDrillPath]);
+
// 处理搜索
const handleSearch = () => {
setCurrentPage(1);
@@ -1437,8 +1477,8 @@ const ConceptCenter = () => {
);
- // 日期选择组件 - 深色主题
- const DateSelector = () => (
+ // 统一控制栏组件 - 日期 + 排序 + 视图切换(单行布局)
+ const ControlBar = () => (
{
borderColor="whiteAlpha.100"
boxShadow="0 4px 20px rgba(0, 0, 0, 0.3)"
>
-
- {/* 使用通用日期选择器组件 - 不显示最新日期提示,由下方单独渲染 */}
- {
- const dateStr = date.toISOString().split('T')[0];
- const previousDate = selectedDate ? selectedDate.toISOString().split('T')[0] : null;
- trackFilterApplied('date', dateStr, previousDate);
- setSelectedDate(date);
- setCurrentPage(1);
- updateUrlParams({ date: dateStr, page: 1 });
- fetchConcepts(searchQuery, 1, date, sortBy);
- }}
- latestTradeDate={latestTradeDate}
- label="交易日期"
- showLatestTradeDateTip={false}
- />
+
+ {/* 左侧:日期选择区 */}
+
+
+ {[
+ { label: '今天', days: 0 },
+ { label: '昨天', days: 1 },
+ { label: '一周前', days: 7 },
+ { label: '一月前', days: 30 },
+ ].map(({ label, days }) => {
+ // 判断是否选中当前按钮
+ const targetDate = new Date();
+ targetDate.setDate(targetDate.getDate() - days);
+ const isSelected = selectedDate &&
+ selectedDate.toDateString() === targetDate.toDateString();
- {/* 快捷按钮 - 紧跟日期选择器 */}
-
-
+ {
+ const dateStr = date.toISOString().split('T')[0];
+ const previousDate = selectedDate ? selectedDate.toISOString().split('T')[0] : null;
+ trackFilterApplied('date', dateStr, previousDate);
+ setSelectedDate(date);
+ setCurrentPage(1);
+ updateUrlParams({ date: dateStr, page: 1 });
+ fetchConcepts(searchQuery, 1, date, sortBy);
}}
- transition="all 0.2s"
- >
- 今天
-
- handleQuickDateSelect(1)}
- bg="whiteAlpha.100"
- color="white"
- borderRadius="full"
- border="1px solid"
- borderColor="whiteAlpha.200"
- px={4}
- _hover={{
- bg: 'purple.500',
- borderColor: 'purple.500',
- boxShadow: '0 0 12px rgba(139, 92, 246, 0.4)',
- }}
- transition="all 0.2s"
- >
- 昨天
-
- handleQuickDateSelect(7)}
- bg="whiteAlpha.100"
- color="white"
- borderRadius="full"
- border="1px solid"
- borderColor="whiteAlpha.200"
- px={4}
- _hover={{
- bg: 'purple.500',
- borderColor: 'purple.500',
- boxShadow: '0 0 12px rgba(139, 92, 246, 0.4)',
- }}
- transition="all 0.2s"
- >
- 一周前
-
- handleQuickDateSelect(30)}
- bg="whiteAlpha.100"
- color="white"
- borderRadius="full"
- border="1px solid"
- borderColor="whiteAlpha.200"
- px={4}
- _hover={{
- bg: 'purple.500',
- borderColor: 'purple.500',
- boxShadow: '0 0 12px rgba(139, 92, 246, 0.4)',
- }}
- transition="all 0.2s"
- >
- 一月前
-
-
+ latestTradeDate={latestTradeDate}
+ label="更多"
+ showIcon={false}
+ showLatestTradeDateTip={false}
+ inputWidth="130px"
+ size="sm"
+ labelColor="white"
+ />
+ {/* 数据更新提示 */}
+ {latestTradeDate && (
+
+ 更新至 {latestTradeDate.toLocaleDateString('zh-CN')}
+
+ )}
+
- {/* 最新交易日期提示 - 靠右显示 */}
- {latestTradeDate && (
-
-
-
-
- 数据更新至 {latestTradeDate.toLocaleDateString('zh-CN')}
-
-
+ {/* 右侧:刷新 + 排序 + 视图切换 */}
+
+ {/* 刷新按钮 */}
+
+ }
+ onClick={() => fetchConcepts(searchQuery, currentPage, selectedDate, sortBy)}
+ bg="whiteAlpha.100"
+ color="white"
+ border="1px solid"
+ borderColor="whiteAlpha.200"
+ borderRadius="full"
+ _hover={{
+ bg: 'whiteAlpha.200',
+ borderColor: 'whiteAlpha.300',
+ }}
+ transition="all 0.2s"
+ aria-label="刷新"
+ />
- )}
+
+ {/* 排序下拉框 - 仅列表视图显示 */}
+ {viewMode === 'list' && (
+
+ 排序
+
+
+ )}
+
+ {/* 视图切换按钮组 */}
+
+
+ }
+ onClick={() => {
+ if (viewMode !== 'force3d') {
+ trackViewModeChanged('force3d', viewMode);
+ setViewMode('force3d');
+ }
+ }}
+ bg={viewMode === 'force3d' ? 'purple.500' : 'transparent'}
+ color={viewMode === 'force3d' ? 'white' : 'whiteAlpha.700'}
+ borderColor="whiteAlpha.300"
+ _hover={{
+ bg: viewMode === 'force3d' ? 'purple.400' : 'whiteAlpha.100',
+ boxShadow: viewMode === 'force3d' ? '0 0 10px rgba(139, 92, 246, 0.4)' : 'none',
+ }}
+ aria-label="矩形树图"
+ />
+
+
+ }
+ onClick={() => {
+ if (viewMode !== 'hierarchy') {
+ trackViewModeChanged('hierarchy', viewMode);
+ setViewMode('hierarchy');
+ }
+ }}
+ bg={viewMode === 'hierarchy' ? 'purple.500' : 'transparent'}
+ color={viewMode === 'hierarchy' ? 'white' : 'whiteAlpha.700'}
+ borderColor="whiteAlpha.300"
+ _hover={{
+ bg: viewMode === 'hierarchy' ? 'purple.400' : 'whiteAlpha.100',
+ boxShadow: viewMode === 'hierarchy' ? '0 0 10px rgba(139, 92, 246, 0.4)' : 'none',
+ }}
+ aria-label="层级图"
+ />
+
+
+ }
+ onClick={() => {
+ if (viewMode !== 'list') {
+ trackViewModeChanged('list', viewMode);
+ setViewMode('list');
+ }
+ }}
+ bg={viewMode === 'list' ? 'purple.500' : 'transparent'}
+ color={viewMode === 'list' ? 'white' : 'whiteAlpha.700'}
+ borderColor="whiteAlpha.300"
+ _hover={{
+ bg: viewMode === 'list' ? 'purple.400' : 'whiteAlpha.100',
+ boxShadow: viewMode === 'list' ? '0 0 10px rgba(139, 92, 246, 0.4)' : 'none',
+ }}
+ aria-label="列表视图"
+ />
+
+
+
);
@@ -1626,179 +1734,57 @@ const ConceptCenter = () => {
}
/>
- {/* 主内容区域 - padding 由 MainLayout 统一设置 */}
-
-
-
-
+ {/* 主内容区域 - 左右两栏布局 */}
+
+
+ {/* 左侧:控制栏 + 概念内容 */}
+
+ {/* 控制栏 */}
+
+
+
- {/* 双栏布局:左侧概念卡片,右侧统计面板 */}
-
- {/* 左侧概念卡片区域 */}
-
+ {/* 概念内容区域 */}
+
+ {/* 面包屑导航 - 显示当前层级筛选 */}
+
-
-
-
- {/* 排序方式 - 仅在列表视图显示 */}
- {viewMode === 'list' && (
-
-
- 排序方式:
-
- {searchQuery && sortBy === '_score' && (
-
-
-
-
- 智能排序
-
-
-
- )}
-
- )}
-
-
-
- }
- onClick={() => {
- if (viewMode !== 'force3d') {
- trackViewModeChanged('force3d', viewMode);
- setViewMode('force3d');
- }
- }}
- bg={viewMode === 'force3d' ? 'purple.500' : 'transparent'}
- color={viewMode === 'force3d' ? 'white' : 'whiteAlpha.700'}
- borderColor="whiteAlpha.300"
- _hover={{
- bg: viewMode === 'force3d' ? 'purple.400' : 'whiteAlpha.100',
- boxShadow: viewMode === 'force3d' ? '0 0 10px rgba(139, 92, 246, 0.4)' : 'none',
- }}
- aria-label="概念矩形树图"
- />
-
-
- }
- onClick={() => {
- if (viewMode !== 'hierarchy') {
- trackViewModeChanged('hierarchy', viewMode);
- setViewMode('hierarchy');
- }
- }}
- bg={viewMode === 'hierarchy' ? 'purple.500' : 'transparent'}
- color={viewMode === 'hierarchy' ? 'white' : 'whiteAlpha.700'}
- borderColor="whiteAlpha.300"
- _hover={{
- bg: viewMode === 'hierarchy' ? 'purple.400' : 'whiteAlpha.100',
- boxShadow: viewMode === 'hierarchy' ? '0 0 10px rgba(139, 92, 246, 0.4)' : 'none',
- }}
- aria-label="层级图"
- />
-
-
- }
- onClick={() => {
- if (viewMode !== 'list') {
- trackViewModeChanged('list', viewMode);
- setViewMode('list');
- }
- }}
- bg={viewMode === 'list' ? 'purple.500' : 'transparent'}
- color={viewMode === 'list' ? 'white' : 'whiteAlpha.700'}
- borderColor="whiteAlpha.300"
- _hover={{
- bg: viewMode === 'list' ? 'purple.400' : 'whiteAlpha.100',
- boxShadow: viewMode === 'list' ? '0 0 10px rgba(139, 92, 246, 0.4)' : 'none',
- }}
- aria-label="列表视图"
- />
-
-
-
-
-
-
- {/* 面包屑导航 - 显示当前层级筛选 */}
-
-
- {selectedDate && viewMode !== 'hierarchy' && viewMode !== 'force3d' && (
-
-
-
-
- 当前显示 {selectedDate.toLocaleDateString('zh-CN')} 的概念涨跌幅数据
- {searchQuery && ,搜索词:"{searchQuery}"}
-
-
-
- )}
-
- {/* 3D 力导向图视图 */}
- {viewMode === 'force3d' ? (
-
- ) : /* 层级图视图 */
- viewMode === 'hierarchy' ? (
-
- ) : loading ? (
+ {viewMode === 'force3d' ? (
+
+ ) : (
+
+ )}
+
+ ) : loading ? (
{[...Array(12)].map((_, i) => (
@@ -1933,53 +1919,35 @@ const ConceptCenter = () => {
) : null}
+
- {/* 右侧统计面板 */}
+ {/* 右侧:概念统计中心 */}
-
- {hasFeatureAccess('concept_stats_panel') ? (
-
- ) : (
-
-
-
-
-
-
- 概念统计中心
-
-
- 此功能需要Pro版订阅才能使用
-
-
- }
- onClick={() => {
- setUpgradeFeature('pro');
- setUpgradeModalOpen(true);
- }}
- _hover={{ bg: 'blue.400', boxShadow: '0 0 15px rgba(59, 130, 246, 0.5)' }}
- >
- 升级到Pro版
-
-
-
-
- )}
-
+ {hasFeatureAccess('concept_stats_panel') ? (
+
+ ) : (
+
+
+
+
+
+ 概念统计中心需要Pro版订阅
+
+
+
+
+ )}