From d08b6f1725297d509bd4299d1084baeca5665827 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Thu, 8 Jan 2026 19:04:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(Concept):=20=E7=BB=9F=E4=B8=80=E5=9B=BE?= =?UTF-8?q?=E8=A1=A8=E5=AE=B9=E5=99=A8=E4=B8=8E=E5=85=B1=E4=BA=AB=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 ChartContainer 包裹矩形树图和层级图 - 添加 chartDrillPath 共享状态,Tab 切换时导航保持 - 矩形树图/层级图使用不同 contentTopPadding 配置 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/views/Concept/index.js | 620 ++++++++++++++++++------------------- 1 file changed, 294 insertions(+), 326 deletions(-) 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" - > - 今天 - - - - - + 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版订阅才能使用 - - - - - - - )} - + {hasFeatureAccess('concept_stats_panel') ? ( + + ) : ( + + + + + + 概念统计中心需要Pro版订阅 + + + + + )}