From 0a4f06859390c718388e0d82a1cfe23d73411970 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Thu, 8 Jan 2026 19:04:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor(HierarchyView):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E7=8A=B6=E6=80=81=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构状态管理,统一使用 drillPath 格式 - 支持外部 drillPath 控制(externalDrillPath, onDrillPathChange) - 添加 hideNavigation 模式支持 - 面包屑、currentLevel 等从 drillPath 派生 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/views/Concept/components/HierarchyView.js | 390 ++++++++++-------- 1 file changed, 227 insertions(+), 163 deletions(-) diff --git a/src/views/Concept/components/HierarchyView.js b/src/views/Concept/components/HierarchyView.js index bee38174..7da86f6e 100644 --- a/src/views/Concept/components/HierarchyView.js +++ b/src/views/Concept/components/HierarchyView.js @@ -30,8 +30,7 @@ import { Layers, Maximize2, Minimize2, - RefreshCw, - Home, + ArrowLeft, ChevronRight, Brain, Cpu, @@ -350,15 +349,6 @@ const GlassCard = ({ item, onClick, size = 'normal' }) => { )} - - {/* 外链图标 */} - {isLeafConcept && ( - - )} {/* 底部:涨跌幅 */} @@ -392,23 +382,44 @@ const GlassCard = ({ item, onClick, size = 'normal' }) => { - {/* 展开标识 */} + {/* 展开标识 - 使用箭头图标 */} {canDrillDown && ( + + + + )} + + {/* 详情按钮 - 仅在 concept 层显示 */} + {isLeafConcept && ( - 展开 + 详情 > )} @@ -422,6 +433,11 @@ const HierarchyView = ({ apiBaseUrl, onSelectCategory, selectedDate, + // 外部控制的 drillPath(可选,用于状态共享) + externalDrillPath, + onDrillPathChange, + // 是否隐藏导航和背景(由外部 ChartContainer 提供时设为 true) + hideNavigation = false, }) => { const [hierarchy, setHierarchy] = useState([]); const [loading, setLoading] = useState(true); @@ -431,12 +447,40 @@ const HierarchyView = ({ const [tradeDate, setTradeDate] = useState(null); const [isFullscreen, setIsFullscreen] = useState(false); - // 钻取状态 - const [currentLevel, setCurrentLevel] = useState('lv1'); - const [currentLv1, setCurrentLv1] = useState(null); - const [currentLv2, setCurrentLv2] = useState(null); - const [currentLv3, setCurrentLv3] = useState(null); - const [breadcrumbs, setBreadcrumbs] = useState([{ label: '全部分类', level: 'root' }]); + // 内部钻取状态 + const [internalDrillPath, setInternalDrillPath] = useState(null); + + // 支持受控和非受控模式 + const drillPath = externalDrillPath !== undefined ? externalDrillPath : internalDrillPath; + const setDrillPath = onDrillPathChange || setInternalDrillPath; + + // 从 drillPath 派生当前层级和选中项 + const currentLevel = useMemo(() => { + if (!drillPath) return 'lv1'; + if (drillPath.lv3) return 'concept'; + if (drillPath.lv2) return 'lv3'; + if (drillPath.lv1) return 'lv2'; + return 'lv1'; + }, [drillPath]); + + const currentLv1 = useMemo(() => drillPath?.lv1 ? { name: drillPath.lv1 } : null, [drillPath]); + const currentLv2 = useMemo(() => drillPath?.lv2 ? { name: drillPath.lv2 } : null, [drillPath]); + const currentLv3 = useMemo(() => drillPath?.lv3 ? { name: drillPath.lv3 } : null, [drillPath]); + + // 面包屑从 drillPath 派生 + const breadcrumbs = useMemo(() => { + const items = [{ label: '全部分类', level: 'root' }]; + if (drillPath?.lv1) { + items.push({ label: drillPath.lv1, level: 'lv1', data: { name: drillPath.lv1 } }); + } + if (drillPath?.lv2) { + items.push({ label: drillPath.lv2, level: 'lv2', data: { name: drillPath.lv2 } }); + } + if (drillPath?.lv3) { + items.push({ label: drillPath.lv3, level: 'lv3', data: { name: drillPath.lv3 } }); + } + return items; + }, [drillPath]); const isMobile = useBreakpointValue({ base: true, md: false }); @@ -651,67 +695,43 @@ const HierarchyView = ({ logger.info('HierarchyView', '热力图点击', { level: item.level, name: item.name }); if (item.level === 'lv1' && item.children && item.children.length > 0) { - setCurrentLevel('lv2'); - setCurrentLv1(item); - setBreadcrumbs([ - { label: '全部分类', level: 'root' }, - { label: item.name, level: 'lv1', data: item }, - ]); + setDrillPath({ lv1: item.name }); } else if (item.level === 'lv2') { - if (item.children && item.children.length > 0) { - setCurrentLevel('lv3'); - setCurrentLv2(item); - setBreadcrumbs([ - { label: '全部分类', level: 'root' }, - { label: currentLv1.name, level: 'lv1', data: currentLv1 }, - { label: item.name, level: 'lv2', data: item }, - ]); - } else if (item.concepts && item.concepts.length > 0) { - setCurrentLevel('lv3'); - setCurrentLv2(item); - setBreadcrumbs([ - { label: '全部分类', level: 'root' }, - { label: currentLv1.name, level: 'lv1', data: currentLv1 }, - { label: item.name, level: 'lv2', data: item }, - ]); + if ((item.children && item.children.length > 0) || (item.concepts && item.concepts.length > 0)) { + setDrillPath({ lv1: drillPath?.lv1, lv2: item.name }); } } else if (item.level === 'lv3' && item.concepts && item.concepts.length > 0) { - setCurrentLevel('concept'); - setCurrentLv3(item); - setBreadcrumbs([ - { label: '全部分类', level: 'root' }, - { label: currentLv1.name, level: 'lv1', data: currentLv1 }, - { label: currentLv2.name, level: 'lv2', data: currentLv2 }, - { label: item.name, level: 'lv3', data: item }, - ]); + setDrillPath({ lv1: drillPath?.lv1, lv2: drillPath?.lv2, lv3: item.name }); } else if (item.level === 'concept') { // 跳转到概念详情页 const htmlPath = getConceptHtmlUrl(item.name); window.open(htmlPath, '_blank'); } - }, [currentLv1, currentLv2]); + }, [drillPath, setDrillPath]); // 面包屑导航 const handleBreadcrumbClick = useCallback((crumb, index) => { if (crumb.level === 'root') { - setCurrentLevel('lv1'); - setCurrentLv1(null); - setCurrentLv2(null); - setCurrentLv3(null); - setBreadcrumbs([{ label: '全部分类', level: 'root' }]); + setDrillPath(null); } else if (crumb.level === 'lv1') { - setCurrentLevel('lv2'); - setCurrentLv1(crumb.data); - setCurrentLv2(null); - setCurrentLv3(null); - setBreadcrumbs(breadcrumbs.slice(0, index + 1)); + setDrillPath({ lv1: crumb.data.name }); } else if (crumb.level === 'lv2') { - setCurrentLevel('lv3'); - setCurrentLv2(crumb.data); - setCurrentLv3(null); - setBreadcrumbs(breadcrumbs.slice(0, index + 1)); + setDrillPath({ lv1: drillPath?.lv1, lv2: crumb.data.name }); } - }, [breadcrumbs]); + }, [drillPath, setDrillPath]); + + // 返回上一层 + const handleGoBack = useCallback(() => { + if (!drillPath) return; + + if (drillPath.lv3) { + setDrillPath({ lv1: drillPath.lv1, lv2: drillPath.lv2 }); + } else if (drillPath.lv2) { + setDrillPath({ lv1: drillPath.lv1 }); + } else if (drillPath.lv1) { + setDrillPath(null); + } + }, [drillPath, setDrillPath]); // 刷新 const handleRefreshPrice = useCallback(() => { @@ -796,6 +816,22 @@ const HierarchyView = ({ ); } + // 当 hideNavigation 为 true 时,只渲染纯卡片网格内容 + if (hideNavigation) { + return ( + + {currentData.map((item) => ( + + ))} + + ); + } + return ( {/* 面包屑导航 + 工具栏(同一行) */} - - {/* 左侧:面包屑导航 */} - - {breadcrumbs.map((crumb, index) => ( - - {index > 0 && ( - - )} - - - ))} - - - {/* 右侧:工具栏按钮 */} - - {priceLoading && ( - + onClick={handleGoBack} + bg="rgba(255, 255, 255, 0.08)" + backdropFilter={GLASS_BLUR.lg} + border="1px solid" + borderColor="whiteAlpha.100" + color="whiteAlpha.800" + borderRadius="full" + _hover={{ + bg: 'rgba(255, 255, 255, 0.15)', + transform: 'scale(1.05)', + }} + transition="all 0.2s" + /> + )} - - } - onClick={handleRefreshPrice} - isLoading={priceLoading} - bg="whiteAlpha.100" - color="white" - border="1px solid" - borderColor="whiteAlpha.200" - _hover={{ bg: 'whiteAlpha.200' }} - aria-label="刷新涨跌幅" - /> - + + {breadcrumbs.map((crumb, index) => ( + + {index > 0 && ( + + )} + { + if (index < breadcrumbs.length - 1) { + handleBreadcrumbClick(crumb, index); + } + }} + transition="color 0.2s" + > + {crumb.label} + + + ))} + + - - : } - onClick={toggleFullscreen} - bg="whiteAlpha.100" - color="white" - border="1px solid" - borderColor="whiteAlpha.200" - _hover={{ bg: 'whiteAlpha.200' }} - aria-label={isFullscreen ? '退出全屏' : '全屏'} - /> - - - - - {/* 图例说明 */} - - - - - - - - - - - - 平/无数据 - - | - - {currentLevel !== 'concept' ? '点击色块查看下级' : '点击查看概念详情'} - - + {/* 右侧:全屏按钮 */} + + : } + size="sm" + variant="ghost" + onClick={toggleFullscreen} + bg="rgba(255, 255, 255, 0.08)" + backdropFilter={GLASS_BLUR.lg} + border="1px solid" + borderColor="whiteAlpha.100" + color="whiteAlpha.800" + borderRadius="full" + _hover={{ + bg: 'rgba(255, 255, 255, 0.15)', + transform: 'scale(1.05)', + }} + transition="all 0.2s" + aria-label={isFullscreen ? '退出全屏' : '全屏'} + /> + + {/* 热力图网格 */} @@ -929,6 +952,47 @@ const HierarchyView = ({ ))} + {/* 底部图例 */} + + + + + + + + + + + + + + );