From 1f874e9b6d0468812188eb37b9cb0948f7d39b71 Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Fri, 5 Dec 2025 19:05:35 +0800 Subject: [PATCH] update pay ui --- .../Concept/components/ForceGraphView.js | 648 +++++++++++++----- 1 file changed, 472 insertions(+), 176 deletions(-) diff --git a/src/views/Concept/components/ForceGraphView.js b/src/views/Concept/components/ForceGraphView.js index b1f0de73..933089be 100644 --- a/src/views/Concept/components/ForceGraphView.js +++ b/src/views/Concept/components/ForceGraphView.js @@ -1,10 +1,15 @@ /** - * TreemapView - 概念层级矩形树图(支持钻取) + * TreemapView - 概念层级矩形树图(玻璃态深空风格) + * + * 设计理念: + * - 半透明玻璃态数据终端,漂浮在深空中 + * - 极光背景 + 毛玻璃卡片 + * - 光影深度、弥散背景光、极致圆角 * * 特性: - * 1. 默认显示3层(一级 → 二级 → 三级) - * 2. 点击矩形钻取进入,显示该分类下的子级 - * 3. 支持返回上级 + * 1. 默认显示3层(一级 → 二级 → 三级),面积按概念数量 + * 2. 智能文字颜色:浅色背景用深色字,深色背景用浅色字 + * 3. 点击矩形钻取进入,支持返回 * 4. 涨红跌绿颜色映射 */ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; @@ -24,6 +29,7 @@ import { Badge, useBreakpointValue, } from '@chakra-ui/react'; +import { keyframes } from '@emotion/react'; import { FaLayerGroup, FaSync, @@ -39,7 +45,33 @@ import { } from 'react-icons/fa'; import { logger } from '../../../utils/logger'; -// 一级分类颜色映射(基础色) +// 极光动画 +const auroraAnimation = keyframes` + 0%, 100% { + background-position: 0% 50%; + filter: hue-rotate(0deg); + } + 25% { + background-position: 50% 100%; + filter: hue-rotate(15deg); + } + 50% { + background-position: 100% 50%; + filter: hue-rotate(0deg); + } + 75% { + background-position: 50% 0%; + filter: hue-rotate(-15deg); + } +`; + +// 光晕脉冲动画 +const glowPulse = keyframes` + 0%, 100% { opacity: 0.3; transform: scale(1); } + 50% { opacity: 0.6; transform: scale(1.05); } +`; + +// 一级分类颜色映射(基础色 - 半透明玻璃态) const LV1_COLORS = { '人工智能': '#8B5CF6', '半导体': '#3B82F6', @@ -58,27 +90,69 @@ const LV1_COLORS = { '前沿科技': '#38BDF8', }; -// 根据涨跌幅获取颜色(涨红跌绿) +// 根据涨跌幅获取颜色(涨红跌绿 - 玻璃态半透明) const getChangeColor = (value, baseColor = '#64748B') => { if (value === null || value === undefined) return baseColor; - // 涨 - 红色系 - if (value > 7) return '#DC2626'; - if (value > 5) return '#EF4444'; - if (value > 3) return '#F87171'; - if (value > 1) return '#FCA5A5'; - if (value > 0) return '#FECACA'; + // 涨 - 红色系(调整透明度使其更柔和) + if (value > 7) return 'rgba(220, 38, 38, 0.85)'; // 深红 + if (value > 5) return 'rgba(239, 68, 68, 0.8)'; + if (value > 3) return 'rgba(248, 113, 113, 0.75)'; + if (value > 1) return 'rgba(252, 165, 165, 0.7)'; + if (value > 0) return 'rgba(254, 202, 202, 0.65)'; // 跌 - 绿色系 - if (value < -7) return '#15803D'; - if (value < -5) return '#16A34A'; - if (value < -3) return '#22C55E'; - if (value < -1) return '#4ADE80'; - if (value < 0) return '#86EFAC'; + if (value < -7) return 'rgba(21, 128, 61, 0.85)'; // 深绿 + if (value < -5) return 'rgba(22, 163, 74, 0.8)'; + if (value < -3) return 'rgba(34, 197, 94, 0.75)'; + if (value < -1) return 'rgba(74, 222, 128, 0.7)'; + if (value < 0) return 'rgba(134, 239, 172, 0.65)'; return baseColor; }; +// 判断颜色是否为浅色(用于决定文字颜色) +const isLightColor = (color) => { + if (!color) return false; + + // 处理 rgba 格式 + const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); + if (rgbaMatch) { + const [, r, g, b] = rgbaMatch.map(Number); + // 使用相对亮度公式 + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + return luminance > 0.55; + } + + // 处理 hex 格式 + const hex = color.replace('#', ''); + if (hex.length === 6) { + const r = parseInt(hex.substr(0, 2), 16); + const g = parseInt(hex.substr(2, 2), 16); + const b = parseInt(hex.substr(4, 2), 16); + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + return luminance > 0.55; + } + + return false; +}; + +// 获取文字颜色(根据背景色自动适配) +const getTextColor = (bgColor, isTitle = false) => { + if (isLightColor(bgColor)) { + return isTitle ? '#1E293B' : '#334155'; + } + return isTitle ? '#FFFFFF' : '#E2E8F0'; +}; + +// 获取文字阴影(根据背景色自动适配) +const getTextShadow = (bgColor) => { + if (isLightColor(bgColor)) { + return 'rgba(255,255,255,0.8)'; + } + return 'rgba(0,0,0,0.8)'; +}; + // 从 API 返回的名称中提取纯名称 const extractPureName = (apiName) => { if (!apiName) return ''; @@ -197,7 +271,29 @@ const ForceGraphView = ({ } }, [hierarchy, fetchHierarchyPrice]); - // 根据钻取路径构建 Treemap 数据 + // 递归计算概念数量 + const getConceptCount = useCallback((node) => { + if (!node) return 1; + + // 如果是叶子节点(概念),返回1 + if (node.concepts && !node.children) { + return node.concepts.length || 1; + } + + // 如果有直接概念 + let count = node.concepts?.length || 0; + + // 递归计算子节点 + if (node.children) { + node.children.forEach(child => { + count += getConceptCount(child); + }); + } + + return count || 1; + }, []); + + // 根据钻取路径构建 Treemap 数据(使用概念数量作为面积) const treemapData = useMemo(() => { const { lv1Map, lv2Map, lv3Map, leafMap } = priceData; @@ -206,14 +302,16 @@ const ForceGraphView = ({ return hierarchy.map((lv1) => { const lv1BaseColor = LV1_COLORS[lv1.name] || '#8B5CF6'; const lv1Price = lv1Map[lv1.name] || {}; + const bgColor = getChangeColor(lv1Price.avg_change_pct, lv1BaseColor); const lv1Node = { name: lv1.name, - value: lv1Price.stock_count || lv1.concept_count * 10 || 100, + value: lv1.concept_count || getConceptCount(lv1), itemStyle: { - color: getChangeColor(lv1Price.avg_change_pct, lv1BaseColor), - borderColor: '#1E293B', + color: bgColor, + borderColor: 'rgba(255, 255, 255, 0.1)', borderWidth: 2, + borderRadius: 12, }, data: { level: 'lv1', @@ -221,6 +319,7 @@ const ForceGraphView = ({ stockCount: lv1Price.stock_count, conceptCount: lv1.concept_count, baseColor: lv1BaseColor, + bgColor: bgColor, }, children: [], }; @@ -229,22 +328,26 @@ const ForceGraphView = ({ if (lv1.children) { lv1.children.forEach((lv2) => { const lv2Price = lv2Map[lv2.name] || {}; + const lv2BgColor = getChangeColor(lv2Price.avg_change_pct, lv1BaseColor); + const lv2ConceptCount = lv2.concept_count || getConceptCount(lv2); const lv2Node = { name: lv2.name, - value: lv2Price.stock_count || lv2.concept_count * 5 || 50, + value: lv2ConceptCount, itemStyle: { - color: getChangeColor(lv2Price.avg_change_pct, lv1BaseColor), - borderColor: '#334155', + color: lv2BgColor, + borderColor: 'rgba(255, 255, 255, 0.08)', borderWidth: 1, + borderRadius: 8, }, data: { level: 'lv2', parentLv1: lv1.name, changePct: lv2Price.avg_change_pct, stockCount: lv2Price.stock_count, - conceptCount: lv2.concept_count, + conceptCount: lv2ConceptCount, baseColor: lv1BaseColor, + bgColor: lv2BgColor, }, children: [], }; @@ -253,14 +356,17 @@ const ForceGraphView = ({ if (lv2.children) { lv2.children.forEach((lv3) => { const lv3Price = lv3Map[lv3.name] || {}; + const lv3BgColor = getChangeColor(lv3Price.avg_change_pct, lv1BaseColor); + const lv3ConceptCount = lv3.concepts?.length || 1; lv2Node.children.push({ name: lv3.name, - value: lv3Price.stock_count || 30, + value: lv3ConceptCount, itemStyle: { - color: getChangeColor(lv3Price.avg_change_pct, lv1BaseColor), - borderColor: '#475569', + color: lv3BgColor, + borderColor: 'rgba(255, 255, 255, 0.05)', borderWidth: 1, + borderRadius: 6, }, data: { level: 'lv3', @@ -268,8 +374,9 @@ const ForceGraphView = ({ parentLv2: lv2.name, changePct: lv3Price.avg_change_pct, stockCount: lv3Price.stock_count, - conceptCount: lv3.concepts?.length || 0, + conceptCount: lv3ConceptCount, baseColor: lv1BaseColor, + bgColor: lv3BgColor, hasChildren: lv3.concepts && lv3.concepts.length > 0, }, }); @@ -293,22 +400,26 @@ const ForceGraphView = ({ return (lv1.children || []).map((lv2) => { const lv2Price = lv2Map[lv2.name] || {}; + const lv2BgColor = getChangeColor(lv2Price.avg_change_pct, lv1BaseColor); + const lv2ConceptCount = lv2.concept_count || getConceptCount(lv2); const lv2Node = { name: lv2.name, - value: lv2Price.stock_count || lv2.concept_count * 5 || 50, + value: lv2ConceptCount, itemStyle: { - color: getChangeColor(lv2Price.avg_change_pct, lv1BaseColor), - borderColor: '#1E293B', + color: lv2BgColor, + borderColor: 'rgba(255, 255, 255, 0.1)', borderWidth: 2, + borderRadius: 12, }, data: { level: 'lv2', parentLv1: lv1.name, changePct: lv2Price.avg_change_pct, stockCount: lv2Price.stock_count, - conceptCount: lv2.concept_count, + conceptCount: lv2ConceptCount, baseColor: lv1BaseColor, + bgColor: lv2BgColor, }, children: [], }; @@ -317,14 +428,17 @@ const ForceGraphView = ({ if (lv2.children) { lv2.children.forEach((lv3) => { const lv3Price = lv3Map[lv3.name] || {}; + const lv3BgColor = getChangeColor(lv3Price.avg_change_pct, lv1BaseColor); + const lv3ConceptCount = lv3.concepts?.length || 1; const lv3Node = { name: lv3.name, - value: lv3Price.stock_count || 30, + value: lv3ConceptCount, itemStyle: { - color: getChangeColor(lv3Price.avg_change_pct, lv1BaseColor), - borderColor: '#334155', + color: lv3BgColor, + borderColor: 'rgba(255, 255, 255, 0.08)', borderWidth: 1, + borderRadius: 8, }, data: { level: 'lv3', @@ -332,7 +446,9 @@ const ForceGraphView = ({ parentLv2: lv2.name, changePct: lv3Price.avg_change_pct, stockCount: lv3Price.stock_count, + conceptCount: lv3ConceptCount, baseColor: lv1BaseColor, + bgColor: lv3BgColor, }, children: [], }; @@ -341,13 +457,15 @@ const ForceGraphView = ({ if (lv3.concepts) { lv3.concepts.forEach((conceptName) => { const conceptPrice = leafMap[conceptName] || {}; + const conceptBgColor = getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor); lv3Node.children.push({ name: conceptName, - value: conceptPrice.stock_count || 10, + value: 1, itemStyle: { - color: getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor), - borderColor: '#475569', + color: conceptBgColor, + borderColor: 'rgba(255, 255, 255, 0.05)', borderWidth: 1, + borderRadius: 6, }, data: { level: 'concept', @@ -357,6 +475,7 @@ const ForceGraphView = ({ changePct: conceptPrice.avg_change_pct, stockCount: conceptPrice.stock_count, baseColor: lv1BaseColor, + bgColor: conceptBgColor, }, }); }); @@ -370,13 +489,15 @@ const ForceGraphView = ({ if (lv2.concepts) { lv2.concepts.forEach((conceptName) => { const conceptPrice = leafMap[conceptName] || {}; + const conceptBgColor = getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor); lv2Node.children.push({ name: conceptName, - value: conceptPrice.stock_count || 10, + value: 1, itemStyle: { - color: getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor), - borderColor: '#475569', + color: conceptBgColor, + borderColor: 'rgba(255, 255, 255, 0.05)', borderWidth: 1, + borderRadius: 6, }, data: { level: 'concept', @@ -385,6 +506,7 @@ const ForceGraphView = ({ changePct: conceptPrice.avg_change_pct, stockCount: conceptPrice.stock_count, baseColor: lv1BaseColor, + bgColor: conceptBgColor, }, }); }); @@ -410,14 +532,17 @@ const ForceGraphView = ({ if (lv2.children) { lv2.children.forEach((lv3) => { const lv3Price = lv3Map[lv3.name] || {}; + const lv3BgColor = getChangeColor(lv3Price.avg_change_pct, lv1BaseColor); + const lv3ConceptCount = lv3.concepts?.length || 1; const lv3Node = { name: lv3.name, - value: lv3Price.stock_count || 30, + value: lv3ConceptCount, itemStyle: { - color: getChangeColor(lv3Price.avg_change_pct, lv1BaseColor), - borderColor: '#1E293B', + color: lv3BgColor, + borderColor: 'rgba(255, 255, 255, 0.1)', borderWidth: 2, + borderRadius: 12, }, data: { level: 'lv3', @@ -425,7 +550,9 @@ const ForceGraphView = ({ parentLv2: lv2.name, changePct: lv3Price.avg_change_pct, stockCount: lv3Price.stock_count, + conceptCount: lv3ConceptCount, baseColor: lv1BaseColor, + bgColor: lv3BgColor, }, children: [], }; @@ -434,13 +561,15 @@ const ForceGraphView = ({ if (lv3.concepts) { lv3.concepts.forEach((conceptName) => { const conceptPrice = leafMap[conceptName] || {}; + const conceptBgColor = getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor); lv3Node.children.push({ name: conceptName, - value: conceptPrice.stock_count || 10, + value: 1, itemStyle: { - color: getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor), - borderColor: '#334155', + color: conceptBgColor, + borderColor: 'rgba(255, 255, 255, 0.05)', borderWidth: 1, + borderRadius: 8, }, data: { level: 'concept', @@ -450,6 +579,7 @@ const ForceGraphView = ({ changePct: conceptPrice.avg_change_pct, stockCount: conceptPrice.stock_count, baseColor: lv1BaseColor, + bgColor: conceptBgColor, }, }); }); @@ -463,13 +593,15 @@ const ForceGraphView = ({ if (lv2.concepts) { lv2.concepts.forEach((conceptName) => { const conceptPrice = leafMap[conceptName] || {}; + const conceptBgColor = getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor); result.push({ name: conceptName, - value: conceptPrice.stock_count || 10, + value: 1, itemStyle: { - color: getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor), - borderColor: '#1E293B', + color: conceptBgColor, + borderColor: 'rgba(255, 255, 255, 0.1)', borderWidth: 2, + borderRadius: 12, }, data: { level: 'concept', @@ -478,6 +610,7 @@ const ForceGraphView = ({ changePct: conceptPrice.avg_change_pct, stockCount: conceptPrice.stock_count, baseColor: lv1BaseColor, + bgColor: conceptBgColor, }, }); }); @@ -501,13 +634,15 @@ const ForceGraphView = ({ return (lv3.concepts || []).map((conceptName) => { const conceptPrice = leafMap[conceptName] || {}; + const conceptBgColor = getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor); return { name: conceptName, - value: conceptPrice.stock_count || 10, + value: 1, itemStyle: { - color: getChangeColor(conceptPrice.avg_change_pct, lv1BaseColor), - borderColor: '#1E293B', + color: conceptBgColor, + borderColor: 'rgba(255, 255, 255, 0.1)', borderWidth: 2, + borderRadius: 12, }, data: { level: 'concept', @@ -517,50 +652,43 @@ const ForceGraphView = ({ changePct: conceptPrice.avg_change_pct, stockCount: conceptPrice.stock_count, baseColor: lv1BaseColor, + bgColor: conceptBgColor, }, }; }); } return []; - }, [hierarchy, priceData, drillPath]); + }, [hierarchy, priceData, drillPath, getConceptCount]); - // 获取当前显示的层级数 - const currentLevels = useMemo(() => { - if (!drillPath) return 3; - if (drillPath.lv1 && !drillPath.lv2) return 3; - if (drillPath.lv1 && drillPath.lv2 && !drillPath.lv3) return 2; - if (drillPath.lv1 && drillPath.lv2 && drillPath.lv3) return 1; - return 3; - }, [drillPath]); - - // ECharts 配置 + // ECharts 配置 - 玻璃态深空风格 const chartOption = useMemo(() => { - // 根据层级深度设置不同的 levels 配置 + // 玻璃态层级配置 const levels = [ { // 根节点配置 itemStyle: { - borderColor: '#0F172A', + borderColor: 'transparent', borderWidth: 0, - gapWidth: 1, + gapWidth: 4, }, }, { - // 第一层 + // 第一层 - 主要分类 itemStyle: { - borderColor: '#1E293B', + borderColor: 'rgba(255, 255, 255, 0.15)', borderWidth: 3, - gapWidth: 2, + gapWidth: 4, + borderRadius: 16, + shadowBlur: 20, + shadowColor: 'rgba(139, 92, 246, 0.3)', }, upperLabel: { show: true, - height: 30, - color: '#FFFFFF', + height: 32, fontSize: 14, fontWeight: 'bold', - textShadowColor: 'rgba(0,0,0,0.8)', - textShadowBlur: 4, + padding: [0, 8], formatter: (params) => { const data = params.data?.data || {}; const changePct = data.changePct; @@ -569,23 +697,34 @@ const ForceGraphView = ({ : ''; return `${params.name}${changeStr}`; }, + rich: { + name: { + fontSize: 14, + fontWeight: 'bold', + } + } + }, + color: (params) => { + const bgColor = params.data?.data?.bgColor; + return getTextColor(bgColor, true); }, }, { // 第二层 itemStyle: { - borderColor: '#334155', + borderColor: 'rgba(255, 255, 255, 0.1)', borderWidth: 2, - gapWidth: 1, + gapWidth: 3, + borderRadius: 12, + shadowBlur: 10, + shadowColor: 'rgba(139, 92, 246, 0.2)', }, upperLabel: { show: true, - height: 24, - color: '#E2E8F0', + height: 26, fontSize: 12, fontWeight: 'bold', - textShadowColor: 'rgba(0,0,0,0.6)', - textShadowBlur: 3, + padding: [0, 6], formatter: (params) => { const data = params.data?.data || {}; const changePct = data.changePct; @@ -599,49 +738,70 @@ const ForceGraphView = ({ { // 第三层 itemStyle: { - borderColor: '#475569', + borderColor: 'rgba(255, 255, 255, 0.08)', borderWidth: 1, - gapWidth: 1, + gapWidth: 2, + borderRadius: 8, }, label: { show: true, position: 'insideTopLeft', - color: '#F1F5F9', fontSize: 11, - textShadowColor: 'rgba(0,0,0,0.5)', - textShadowBlur: 2, + padding: [4, 6], formatter: (params) => { const data = params.data?.data || {}; + const bgColor = data.bgColor; const changePct = data.changePct; if (changePct !== undefined && changePct !== null) { - return `${params.name}\n${formatChangePercent(changePct)}`; + return `{name|${params.name}}\n{change|${formatChangePercent(changePct)}}`; } return params.name; }, + rich: { + name: { + fontSize: 11, + fontWeight: 'bold', + lineHeight: 16, + }, + change: { + fontSize: 10, + lineHeight: 14, + } + } }, }, { - // 第四层(概念层,只在钻取时显示) + // 第四层(概念层) itemStyle: { - borderColor: '#64748B', + borderColor: 'rgba(255, 255, 255, 0.05)', borderWidth: 1, - gapWidth: 1, + gapWidth: 2, + borderRadius: 6, }, label: { show: true, position: 'insideTopLeft', - color: '#F8FAFC', fontSize: 10, - textShadowColor: 'rgba(0,0,0,0.4)', - textShadowBlur: 2, + padding: [3, 5], formatter: (params) => { const data = params.data?.data || {}; const changePct = data.changePct; if (changePct !== undefined && changePct !== null) { - return `${params.name}\n${formatChangePercent(changePct)}`; + return `{name|${params.name}}\n{change|${formatChangePercent(changePct)}}`; } return params.name; }, + rich: { + name: { + fontSize: 10, + fontWeight: 'bold', + lineHeight: 14, + }, + change: { + fontSize: 9, + lineHeight: 12, + } + } }, }, ]; @@ -650,11 +810,12 @@ const ForceGraphView = ({ backgroundColor: 'transparent', tooltip: { trigger: 'item', - backgroundColor: 'rgba(15, 23, 42, 0.95)', - borderColor: 'rgba(139, 92, 246, 0.5)', + backgroundColor: 'rgba(15, 23, 42, 0.9)', + borderColor: 'rgba(139, 92, 246, 0.4)', borderWidth: 1, - borderRadius: 12, - padding: [12, 16], + borderRadius: 16, + padding: [14, 18], + extraCssText: 'backdrop-filter: blur(20px); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);', textStyle: { color: '#E2E8F0', fontSize: 13, @@ -673,45 +834,48 @@ const ForceGraphView = ({ const changeIcon = changePct > 0 ? '▲' : changePct < 0 ? '▼' : '●'; return ` -
-
+
+
${levelMap[data.level] || '分类'}
-
+
${params.name}
${changePct !== undefined && changePct !== null ? `
${changeIcon} - + ${formatChangePercent(changePct)}
` : ''} -
- ${data.stockCount ? `${data.stockCount} 只股票` : ''} - ${data.conceptCount ? ` · ${data.conceptCount} 个概念` : ''} +
+ ${data.stockCount ? `📊 ${data.stockCount} 只股票` : ''} + ${data.conceptCount ? `📁 ${data.conceptCount} 个概念` : ''}
${data.level === 'concept' ? ` -
+
🔗 点击查看概念详情
` : data.level !== 'concept' ? ` -
+
📂 点击进入查看子分类
` : ''} @@ -723,36 +887,52 @@ const ForceGraphView = ({ { type: 'treemap', data: treemapData, - left: 0, + left: 8, top: 60, - right: 0, + right: 8, bottom: 50, roam: false, - nodeClick: false, // 禁用内置的点击行为,我们自己处理 + nodeClick: false, breadcrumb: { - show: false, // 隐藏内置面包屑,使用自定义的 + show: false, }, levels: levels, label: { show: true, formatter: '{b}', }, + // 动态设置文字颜色 + labelLayout: (params) => { + return {}; + }, itemStyle: { - borderRadius: 4, + borderRadius: 8, }, emphasis: { itemStyle: { - shadowBlur: 20, - shadowColor: 'rgba(139, 92, 246, 0.6)', + shadowBlur: 30, + shadowColor: 'rgba(139, 92, 246, 0.5)', + borderColor: 'rgba(255, 255, 255, 0.3)', }, }, animation: true, - animationDuration: 500, + animationDuration: 600, animationEasing: 'cubicOut', }, ], + // 使用 visualMap 来控制文字颜色 + visualMap: { + show: false, + type: 'continuous', + min: -10, + max: 10, + dimension: 'value', + inRange: { + // 这里可以添加颜色映射 + }, + }, }; - }, [treemapData, currentLevels]); + }, [treemapData]); // 图表事件 const onChartEvents = useMemo(() => ({ @@ -832,10 +1012,30 @@ const ForceGraphView = ({ if (loading) { return ( -
+
- - 正在构建矩形树图... + + + + + 正在构建矩形树图...
); @@ -843,11 +1043,24 @@ const ForceGraphView = ({ if (error) { return ( -
+
加载失败:{error} - @@ -864,14 +1077,61 @@ const ForceGraphView = ({ right={isFullscreen ? 0 : 'auto'} bottom={isFullscreen ? 0 : 'auto'} zIndex={isFullscreen ? 1000 : 'auto'} - bg="linear-gradient(135deg, #0F172A 0%, #1E1B4B 50%, #0F172A 100%)" - borderRadius={isFullscreen ? '0' : '2xl'} + borderRadius={isFullscreen ? '0' : '3xl'} overflow="hidden" border={isFullscreen ? 'none' : '1px solid'} - borderColor="whiteAlpha.200" + borderColor="whiteAlpha.100" h={containerHeight} + bg="transparent" > - {/* 顶部工具栏 */} + {/* 极光背景层 */} + + + {/* 弥散光晕层 */} + + + + + {/* 顶部工具栏 - 毛玻璃风格 */} } onClick={handleGoBack} - bg="rgba(0, 0, 0, 0.7)" + bg="rgba(255, 255, 255, 0.1)" + backdropFilter="blur(20px)" color="white" border="1px solid" borderColor="whiteAlpha.200" - _hover={{ bg: 'purple.500', borderColor: 'purple.400' }} + borderRadius="full" + _hover={{ + bg: 'rgba(139, 92, 246, 0.4)', + borderColor: 'purple.400', + transform: 'scale(1.05)', + }} + transition="all 0.2s" aria-label="返回" /> )} @@ -920,13 +1187,13 @@ const ForceGraphView = ({ {/* 面包屑导航 */} {breadcrumbItems.map((item, index) => ( @@ -940,6 +1207,7 @@ const ForceGraphView = ({ fontWeight={index === breadcrumbItems.length - 1 ? 'bold' : 'normal'} cursor={index < breadcrumbItems.length - 1 ? 'pointer' : 'default'} _hover={index < breadcrumbItems.length - 1 ? { color: 'white' } : {}} + transition="color 0.2s" onClick={() => { if (index < breadcrumbItems.length - 1) { setDrillPath(item.path); @@ -964,11 +1232,18 @@ const ForceGraphView = ({ size="sm" icon={} onClick={handleGoHome} - bg="rgba(0, 0, 0, 0.7)" + bg="rgba(255, 255, 255, 0.1)" + backdropFilter="blur(20px)" color="white" border="1px solid" borderColor="whiteAlpha.200" - _hover={{ bg: 'purple.500', borderColor: 'purple.400' }} + borderRadius="full" + _hover={{ + bg: 'rgba(139, 92, 246, 0.4)', + borderColor: 'purple.400', + transform: 'scale(1.05)', + }} + transition="all 0.2s" aria-label="返回全部" /> @@ -980,11 +1255,18 @@ const ForceGraphView = ({ icon={} onClick={handleRefresh} isLoading={priceLoading} - bg="rgba(0, 0, 0, 0.7)" + bg="rgba(255, 255, 255, 0.1)" + backdropFilter="blur(20px)" color="white" border="1px solid" borderColor="whiteAlpha.200" - _hover={{ bg: 'whiteAlpha.200', borderColor: 'purple.400' }} + borderRadius="full" + _hover={{ + bg: 'rgba(255, 255, 255, 0.2)', + borderColor: 'whiteAlpha.300', + transform: 'scale(1.05)', + }} + transition="all 0.2s" aria-label="刷新" /> @@ -994,18 +1276,25 @@ const ForceGraphView = ({ size="sm" icon={isFullscreen ? : } onClick={toggleFullscreen} - bg="rgba(0, 0, 0, 0.7)" + bg="rgba(255, 255, 255, 0.1)" + backdropFilter="blur(20px)" color="white" border="1px solid" borderColor="whiteAlpha.200" - _hover={{ bg: 'whiteAlpha.200', borderColor: 'purple.400' }} + borderRadius="full" + _hover={{ + bg: 'rgba(255, 255, 255, 0.2)', + borderColor: 'whiteAlpha.300', + transform: 'scale(1.05)', + }} + transition="all 0.2s" aria-label={isFullscreen ? '退出全屏' : '全屏'} /> - {/* 底部图例 */} + {/* 底部图例 - 毛玻璃风格 */} - - - - - - + + + + + + + + + @@ -1052,15 +1348,15 @@ const ForceGraphView = ({ pointerEvents="none" > - + 点击分类进入 · 点击概念查看详情