diff --git a/src/views/Concept/components/HierarchyView.js b/src/views/Concept/components/HierarchyView.js
index 21b65489..7700f1eb 100644
--- a/src/views/Concept/components/HierarchyView.js
+++ b/src/views/Concept/components/HierarchyView.js
@@ -1,14 +1,15 @@
/**
* HierarchyView - 概念层级热力图视图
*
- * 使用 ECharts Treemap 实现热力图效果
+ * 使用 CSS Grid + Chakra UI 实现热力图效果
* 特性:
- * 1. 炫酷的矩形树图/热力图展示
+ * 1. 炫酷的矩形热力图展示,涨红跌绿背景色
* 2. 点击 lv1 进入 lv2,点击 lv2 进入 lv3,层层钻取
* 3. 集成 /hierarchy/price 接口获取实时涨跌幅
- * 4. 支持面包屑导航返回上级
+ * 4. 每个分类有独特图标
+ * 5. 支持面包屑导航返回上级
*/
-import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
+import React, { useState, useEffect, useCallback, useMemo } from 'react';
import {
Box,
VStack,
@@ -23,8 +24,9 @@ import {
useBreakpointValue,
Tooltip,
IconButton,
+ SimpleGrid,
+ keyframes,
} from '@chakra-ui/react';
-import ReactECharts from 'echarts-for-react';
import {
FaLayerGroup,
FaExpand,
@@ -32,38 +34,124 @@ import {
FaSync,
FaHome,
FaChevronRight,
+ FaBrain,
+ FaMicrochip,
+ FaRobot,
+ FaMobileAlt,
+ FaCar,
+ FaBolt,
+ FaRocket,
+ FaShieldAlt,
+ FaGlobe,
+ FaIndustry,
+ FaShoppingCart,
+ FaCoins,
+ FaHeartbeat,
+ FaAtom,
+ FaLightbulb,
+ FaArrowUp,
+ FaArrowDown,
+ FaCubes,
+ FaServer,
+ FaCode,
+ FaMagic,
+ FaEye,
+ FaPlane,
+ FaSatellite,
+ FaBatteryFull,
+ FaSolarPanel,
+ FaWind,
} from 'react-icons/fa';
import { logger } from '../../../utils/logger';
-// 一级分类颜色映射
-const LV1_COLORS = {
- '人工智能': ['#8B5CF6', '#A78BFA', '#C4B5FD'],
- '半导体': ['#3B82F6', '#60A5FA', '#93C5FD'],
- '机器人': ['#10B981', '#34D399', '#6EE7B7'],
- '消费电子': ['#EC4899', '#F472B6', '#F9A8D4'],
- '智能驾驶与汽车': ['#F97316', '#FB923C', '#FDBA74'],
- '新能源与电力': ['#22C55E', '#4ADE80', '#86EFAC'],
- '空天经济': ['#06B6D4', '#22D3EE', '#67E8F9'],
- '国防军工': ['#EF4444', '#F87171', '#FCA5A5'],
- '政策与主题': ['#F59E0B', '#FBBF24', '#FCD34D'],
- '周期与材料': ['#6B7280', '#9CA3AF', '#D1D5DB'],
- '大消费': ['#F472B6', '#F9A8D4', '#FBCFE8'],
- '数字经济与金融科技': ['#6366F1', '#818CF8', '#A5B4FC'],
- '全球宏观与贸易': ['#14B8A6', '#2DD4BF', '#5EEAD4'],
- '医药健康': ['#84CC16', '#A3E635', '#BEF264'],
- '前沿科技': ['#A855F7', '#C084FC', '#D8B4FE'],
+// 一级分类图标映射
+const LV1_ICONS = {
+ '人工智能': FaBrain,
+ '半导体': FaMicrochip,
+ '机器人': FaRobot,
+ '消费电子': FaMobileAlt,
+ '智能驾驶与汽车': FaCar,
+ '新能源与电力': FaBolt,
+ '空天经济': FaRocket,
+ '国防军工': FaShieldAlt,
+ '政策与主题': FaGlobe,
+ '周期与材料': FaIndustry,
+ '大消费': FaShoppingCart,
+ '数字经济与金融科技': FaCoins,
+ '全球宏观与贸易': FaGlobe,
+ '医药健康': FaHeartbeat,
+ '前沿科技': FaAtom,
};
-// 获取涨跌幅颜色(红涨绿跌)
-const getChangeColor = (value) => {
- if (value === null || value === undefined) return '#9CA3AF';
- if (value > 3) return '#DC2626';
- if (value > 1) return '#EF4444';
- if (value > 0) return '#F87171';
- if (value < -3) return '#15803D';
- if (value < -1) return '#22C55E';
- if (value < 0) return '#4ADE80';
- return '#9CA3AF';
+// 二级分类图标映射
+const LV2_ICONS = {
+ 'AI基础设施': FaServer,
+ 'AI模型与软件': FaCode,
+ 'AI应用': FaMagic,
+ '半导体设备': FaCubes,
+ '半导体材料': FaAtom,
+ '芯片设计与制造': FaMicrochip,
+ '先进封装': FaCubes,
+ '人形机器人整机': FaRobot,
+ '机器人核心零部件': FaCubes,
+ '其他类型机器人': FaRobot,
+ '智能终端': FaMobileAlt,
+ 'XR与空间计算': FaEye,
+ '华为产业链': FaMobileAlt,
+ '自动驾驶解决方案': FaCar,
+ '智能汽车产业链': FaCar,
+ '车路协同': FaCar,
+ '新型电池技术': FaBatteryFull,
+ '电力设备与电网': FaBolt,
+ '清洁能源': FaSolarPanel,
+ '低空经济': FaPlane,
+ '商业航天': FaSatellite,
+ '无人作战与信息化': FaShieldAlt,
+ '海军装备': FaShieldAlt,
+ '军贸出海': FaGlobe,
+};
+
+// 一级分类基础颜色(用于渐变)
+const LV1_BASE_COLORS = {
+ '人工智能': { from: '#8B5CF6', to: '#A78BFA' },
+ '半导体': { from: '#3B82F6', to: '#60A5FA' },
+ '机器人': { from: '#10B981', to: '#34D399' },
+ '消费电子': { from: '#EC4899', to: '#F472B6' },
+ '智能驾驶与汽车': { from: '#F97316', to: '#FB923C' },
+ '新能源与电力': { from: '#22C55E', to: '#4ADE80' },
+ '空天经济': { from: '#06B6D4', to: '#22D3EE' },
+ '国防军工': { from: '#EF4444', to: '#F87171' },
+ '政策与主题': { from: '#F59E0B', to: '#FBBF24' },
+ '周期与材料': { from: '#6B7280', to: '#9CA3AF' },
+ '大消费': { from: '#F472B6', to: '#F9A8D4' },
+ '数字经济与金融科技': { from: '#6366F1', to: '#818CF8' },
+ '全球宏观与贸易': { from: '#14B8A6', to: '#2DD4BF' },
+ '医药健康': { from: '#84CC16', to: '#A3E635' },
+ '前沿科技': { from: '#A855F7', to: '#C084FC' },
+};
+
+// 根据涨跌幅获取背景色(涨红跌绿渐变)
+const getChangeBgColor = (value, baseColor = null) => {
+ if (value === null || value === undefined) {
+ // 无数据时使用基础色
+ if (baseColor) {
+ return `linear-gradient(135deg, ${baseColor.from} 0%, ${baseColor.to} 100%)`;
+ }
+ return 'linear-gradient(135deg, #6B7280 0%, #9CA3AF 100%)';
+ }
+
+ // 涨跌幅越大,颜色越深
+ if (value > 5) return 'linear-gradient(135deg, #991B1B 0%, #DC2626 100%)';
+ if (value > 3) return 'linear-gradient(135deg, #B91C1C 0%, #EF4444 100%)';
+ if (value > 1) return 'linear-gradient(135deg, #DC2626 0%, #F87171 100%)';
+ if (value > 0) return 'linear-gradient(135deg, #EF4444 0%, #FCA5A5 100%)';
+ if (value < -5) return 'linear-gradient(135deg, #14532D 0%, #16A34A 100%)';
+ if (value < -3) return 'linear-gradient(135deg, #166534 0%, #22C55E 100%)';
+ if (value < -1) return 'linear-gradient(135deg, #16A34A 0%, #4ADE80 100%)';
+ if (value < 0) return 'linear-gradient(135deg, #22C55E 0%, #86EFAC 100%)';
+
+ // 平盘
+ return 'linear-gradient(135deg, #6B7280 0%, #9CA3AF 100%)';
};
// 格式化涨跌幅
@@ -73,13 +161,173 @@ const formatChangePercent = (value) => {
return value > 0 ? `+${formatted}%` : value < 0 ? `-${formatted}%` : '0.00%';
};
-// 获取 lv1 的颜色
-const getLv1Color = (name, index = 0) => {
- const colors = LV1_COLORS[name];
- if (colors) return colors[Math.min(index, colors.length - 1)];
- // 默认颜色
- const defaultColors = ['#8B5CF6', '#A78BFA', '#C4B5FD'];
- return defaultColors[Math.min(index, defaultColors.length - 1)];
+// 获取图标
+const getIcon = (name, level) => {
+ if (level === 'lv1') {
+ return LV1_ICONS[name] || FaLayerGroup;
+ }
+ if (level === 'lv2') {
+ return LV2_ICONS[name] || FaCubes;
+ }
+ return FaLightbulb;
+};
+
+// 脉冲动画
+const pulseKeyframes = keyframes`
+ 0%, 100% { transform: scale(1); }
+ 50% { transform: scale(1.02); }
+`;
+
+/**
+ * 单个热力图块组件
+ */
+const HeatmapBlock = ({ item, onClick, size = 'normal' }) => {
+ const isMobile = useBreakpointValue({ base: true, md: false });
+ const hasChange = item.avg_change_pct !== null && item.avg_change_pct !== undefined;
+ const isPositive = hasChange && item.avg_change_pct > 0;
+ const isNegative = hasChange && item.avg_change_pct < 0;
+ const isLargeChange = hasChange && Math.abs(item.avg_change_pct) > 3;
+
+ const IconComponent = getIcon(item.name, item.level);
+ const baseColor = item.parentLv1
+ ? LV1_BASE_COLORS[item.parentLv1]
+ : LV1_BASE_COLORS[item.name];
+
+ // 根据 size 调整高度
+ const heightMap = {
+ large: { base: '140px', md: '180px' },
+ normal: { base: '120px', md: '150px' },
+ small: { base: '100px', md: '120px' },
+ };
+
+ return (
+ onClick(item)}
+ position="relative"
+ overflow="hidden"
+ minH={heightMap[size]}
+ transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
+ boxShadow="0 4px 15px rgba(0, 0, 0, 0.2)"
+ _hover={{
+ transform: 'translateY(-4px) scale(1.02)',
+ boxShadow: '0 12px 30px rgba(0, 0, 0, 0.3)',
+ }}
+ animation={isLargeChange ? `${pulseKeyframes} 2s infinite` : 'none'}
+ >
+ {/* 背景装饰 */}
+
+
+
+ {/* 内容 */}
+
+ {/* 顶部:图标和名称 */}
+
+
+
+
+
+
+ {item.name}
+
+ {item.concept_count && (
+
+ {item.concept_count} 个概念
+
+ )}
+
+
+
+ {/* 底部:涨跌幅 */}
+
+
+ {item.stock_count && (
+
+ {item.stock_count} 只股票
+
+ )}
+
+
+
+ {hasChange && (
+
+ )}
+
+ {formatChangePercent(item.avg_change_pct)}
+
+
+
+
+
+ {/* 可点击提示 */}
+ {(item.children?.length > 0 || item.concepts?.length > 0) && (
+
+ 点击展开
+
+ )}
+
+ );
};
/**
@@ -99,13 +347,11 @@ const HierarchyView = ({
const [isFullscreen, setIsFullscreen] = useState(false);
// 钻取状态
- const [currentLevel, setCurrentLevel] = useState('lv1'); // 'lv1' | 'lv2' | 'lv3'
+ const [currentLevel, setCurrentLevel] = useState('lv1');
const [currentLv1, setCurrentLv1] = useState(null);
const [currentLv2, setCurrentLv2] = useState(null);
const [breadcrumbs, setBreadcrumbs] = useState([{ label: '全部分类', level: 'root' }]);
- const chartRef = useRef(null);
- const containerRef = useRef(null);
const isMobile = useBreakpointValue({ base: true, md: false });
// 获取层级结构数据
@@ -151,7 +397,6 @@ const HierarchyView = ({
const data = await response.json();
- // 构建映射表
const lv1Map = {};
const lv2Map = {};
const lv3Map = {};
@@ -197,37 +442,28 @@ const HierarchyView = ({
const { lv1Map, lv2Map, lv3Map } = priceData;
if (currentLevel === 'lv1') {
- // 显示所有 lv1
- return hierarchy.map((lv1, index) => {
+ return hierarchy.map((lv1) => {
const price = lv1Map[lv1.name] || {};
return {
name: lv1.name,
- value: lv1.concept_count || 10,
id: lv1.id,
level: 'lv1',
concept_count: lv1.concept_count,
stock_count: price.stock_count,
avg_change_pct: price.avg_change_pct,
children: lv1.children,
- itemStyle: {
- color: getLv1Color(lv1.name, 0),
- borderColor: '#fff',
- borderWidth: 2,
- },
};
});
}
if (currentLevel === 'lv2' && currentLv1) {
- // 显示选中 lv1 下的 lv2
const lv1Data = hierarchy.find(h => h.name === currentLv1.name);
if (!lv1Data || !lv1Data.children) return [];
- return lv1Data.children.map((lv2, index) => {
+ return lv1Data.children.map((lv2) => {
const price = lv2Map[lv2.name] || {};
return {
name: lv2.name,
- value: lv2.concept_count || 5,
id: lv2.id,
level: 'lv2',
parentLv1: currentLv1.name,
@@ -236,30 +472,22 @@ const HierarchyView = ({
avg_change_pct: price.avg_change_pct,
children: lv2.children,
concepts: lv2.concepts,
- itemStyle: {
- color: getLv1Color(currentLv1.name, index % 3),
- borderColor: '#fff',
- borderWidth: 2,
- },
};
});
}
if (currentLevel === 'lv3' && currentLv1 && currentLv2) {
- // 显示选中 lv2 下的 lv3 或概念
const lv1Data = hierarchy.find(h => h.name === currentLv1.name);
if (!lv1Data || !lv1Data.children) return [];
const lv2Data = lv1Data.children.find(h => h.name === currentLv2.name);
if (!lv2Data) return [];
- // 如果有 lv3 子级
if (lv2Data.children && lv2Data.children.length > 0) {
- return lv2Data.children.map((lv3, index) => {
+ return lv2Data.children.map((lv3) => {
const price = lv3Map[lv3.name] || {};
return {
name: lv3.name,
- value: lv3.concept_count || 3,
id: lv3.id,
level: 'lv3',
parentLv1: currentLv1.name,
@@ -268,28 +496,16 @@ const HierarchyView = ({
stock_count: price.stock_count,
avg_change_pct: price.avg_change_pct,
concepts: lv3.concepts,
- itemStyle: {
- color: getLv1Color(currentLv1.name, index % 3),
- borderColor: '#fff',
- borderWidth: 2,
- },
};
});
}
- // 如果直接是概念列表
if (lv2Data.concepts && lv2Data.concepts.length > 0) {
- return lv2Data.concepts.map((concept, index) => ({
+ return lv2Data.concepts.map((concept) => ({
name: concept,
- value: 1,
level: 'concept',
parentLv1: currentLv1.name,
parentLv2: currentLv2.name,
- itemStyle: {
- color: getLv1Color(currentLv1.name, index % 3),
- borderColor: '#fff',
- borderWidth: 1,
- },
}));
}
@@ -299,172 +515,31 @@ const HierarchyView = ({
return [];
}, [hierarchy, priceData, currentLevel, currentLv1, currentLv2]);
- // ECharts 配置
- const chartOption = useMemo(() => {
- if (!currentData || currentData.length === 0) return null;
-
- return {
- tooltip: {
- trigger: 'item',
- formatter: (params) => {
- const data = params.data || {};
- let content = `
${data.name}
`;
-
- if (data.avg_change_pct !== undefined && data.avg_change_pct !== null) {
- const color = getChangeColor(data.avg_change_pct);
- content += `平均涨跌: ${formatChangePercent(data.avg_change_pct)}
`;
- }
-
- if (data.concept_count !== undefined) {
- content += `概念数量: ${data.concept_count}
`;
- }
-
- if (data.stock_count !== undefined) {
- content += `成分股数: ${data.stock_count}
`;
- }
-
- const levelMap = { lv1: '一级分类', lv2: '二级分类', lv3: '三级分类', concept: '概念' };
- if (data.level) {
- content += `${levelMap[data.level] || ''}
`;
- }
-
- // 提示可点击
- if (data.level && data.level !== 'concept' && data.children) {
- content += `👆 点击查看下级分类
`;
- }
-
- return content;
- },
- backgroundColor: 'rgba(255, 255, 255, 0.98)',
- borderColor: '#E5E7EB',
- borderWidth: 1,
- padding: [12, 16],
- textStyle: {
- color: '#1F2937',
- fontSize: 12,
- },
- extraCssText: 'box-shadow: 0 4px 20px rgba(0,0,0,0.15); border-radius: 8px;',
- },
- series: [
- {
- type: 'treemap',
- data: currentData,
- width: '100%',
- height: '100%',
- roam: false,
- nodeClick: false, // 禁用默认钻取,使用自定义
- breadcrumb: {
- show: false, // 使用自定义面包屑
- },
- label: {
- show: true,
- formatter: (params) => {
- const data = params.data || {};
- const name = data.name || '';
- const changeStr = data.avg_change_pct !== undefined && data.avg_change_pct !== null
- ? formatChangePercent(data.avg_change_pct)
- : '';
-
- // 根据区块大小决定显示内容
- if (params.value < 3) {
- return name.length > 4 ? name.slice(0, 4) + '...' : name;
- }
-
- if (changeStr) {
- return `{name|${name}}\n{change|${changeStr}}`;
- }
- return `{name|${name}}`;
- },
- rich: {
- name: {
- fontSize: isMobile ? 12 : 14,
- fontWeight: 'bold',
- color: '#fff',
- textShadowColor: 'rgba(0,0,0,0.3)',
- textShadowBlur: 2,
- lineHeight: isMobile ? 18 : 22,
- },
- change: {
- fontSize: isMobile ? 14 : 18,
- fontWeight: 'bold',
- color: '#fff',
- textShadowColor: 'rgba(0,0,0,0.5)',
- textShadowBlur: 3,
- lineHeight: isMobile ? 20 : 26,
- },
- },
- position: 'inside',
- verticalAlign: 'middle',
- align: 'center',
- },
- upperLabel: {
- show: false,
- },
- itemStyle: {
- borderColor: '#fff',
- borderWidth: 3,
- gapWidth: 3,
- borderRadius: 4,
- },
- emphasis: {
- itemStyle: {
- shadowBlur: 20,
- shadowColor: 'rgba(0, 0, 0, 0.3)',
- },
- label: {
- fontSize: isMobile ? 14 : 16,
- },
- },
- levels: [
- {
- itemStyle: {
- borderColor: '#fff',
- borderWidth: 4,
- gapWidth: 4,
- },
- },
- ],
- animationDuration: 500,
- animationEasing: 'cubicOut',
- },
- ],
- };
- }, [currentData, isMobile]);
-
// 处理点击事件 - 钻取
- const handleChartClick = useCallback((params) => {
- const data = params.data;
- if (!data) return;
+ const handleBlockClick = useCallback((item) => {
+ logger.info('HierarchyView', '热力图点击', { level: item.level, name: item.name });
- logger.info('HierarchyView', '热力图点击', { level: data.level, name: data.name });
-
- if (data.level === 'lv1' && data.children && data.children.length > 0) {
- // 进入 lv2
+ if (item.level === 'lv1' && item.children && item.children.length > 0) {
setCurrentLevel('lv2');
- setCurrentLv1(data);
+ setCurrentLv1(item);
setBreadcrumbs([
{ label: '全部分类', level: 'root' },
- { label: data.name, level: 'lv1', data },
+ { label: item.name, level: 'lv1', data: item },
]);
- } else if (data.level === 'lv2') {
- // 检查是否有 lv3 或概念
- if ((data.children && data.children.length > 0) || (data.concepts && data.concepts.length > 0)) {
+ } else if (item.level === 'lv2') {
+ if ((item.children && item.children.length > 0) || (item.concepts && item.concepts.length > 0)) {
setCurrentLevel('lv3');
- setCurrentLv2(data);
+ setCurrentLv2(item);
setBreadcrumbs([
{ label: '全部分类', level: 'root' },
{ label: currentLv1.name, level: 'lv1', data: currentLv1 },
- { label: data.name, level: 'lv2', data },
+ { label: item.name, level: 'lv2', data: item },
]);
}
- } else if (data.level === 'lv3' || data.level === 'concept') {
- // 最底层,可以触发筛选或者其他操作
- // 这里可以选择不做任何操作,或者提示用户
- logger.info('HierarchyView', '已到达最底层', { name: data.name });
}
}, [currentLv1]);
- // 面包屑导航 - 返回上级
+ // 面包屑导航
const handleBreadcrumbClick = useCallback((crumb, index) => {
if (crumb.level === 'root') {
setCurrentLevel('lv1');
@@ -489,22 +564,28 @@ const HierarchyView = ({
setIsFullscreen(prev => !prev);
}, []);
- // 图表事件
- const chartEvents = useMemo(() => ({
- click: handleChartClick,
- }), [handleChartClick]);
-
// 获取当前层级标题
const getCurrentTitle = () => {
- if (currentLevel === 'lv1') return '一级分类概览';
- if (currentLevel === 'lv2' && currentLv1) return `${currentLv1.name} - 二级分类`;
- if (currentLevel === 'lv3' && currentLv2) return `${currentLv2.name} - 三级分类`;
+ if (currentLevel === 'lv1') return '概念分类热力图';
+ if (currentLevel === 'lv2' && currentLv1) return `${currentLv1.name}`;
+ if (currentLevel === 'lv3' && currentLv2) return `${currentLv2.name}`;
return '概念分类';
};
+ // 计算列数
+ const getGridColumns = () => {
+ if (currentLevel === 'lv1') {
+ return { base: 2, md: 3, lg: 4 };
+ }
+ if (currentLevel === 'lv2') {
+ return { base: 2, md: 3, lg: 4 };
+ }
+ return { base: 2, md: 3, lg: 4 };
+ };
+
if (loading) {
return (
-
+
正在加载概念层级...
@@ -515,7 +596,7 @@ const HierarchyView = ({
if (error) {
return (
-
+
加载失败:{error}
@@ -527,9 +608,9 @@ const HierarchyView = ({
);
}
- if (!chartOption) {
+ if (currentData.length === 0) {
return (
-
+
暂无层级数据
@@ -540,41 +621,40 @@ const HierarchyView = ({
return (
{/* 工具栏 */}
-
-
-
+
+
+
{getCurrentTitle()}
{tradeDate && (
-
+
{tradeDate}
)}
{priceLoading && (
-
+
)}
- {/* 刷新涨跌幅 */}
- {/* 全屏切换 */}
@@ -624,6 +704,7 @@ const HierarchyView = ({
onClick={() => handleBreadcrumbClick(crumb, index)}
isDisabled={index === breadcrumbs.length - 1}
fontWeight={index === breadcrumbs.length - 1 ? 'bold' : 'medium'}
+ borderRadius="lg"
>
{crumb.label}
@@ -631,71 +712,69 @@ const HierarchyView = ({
))}
- {/* 操作提示 */}
-
- 👆 点击色块 查看下级分类 |
- 区块大小表示概念数量 |
- 红涨
- 绿跌
-
-
- {/* ECharts 热力图 */}
-
-
-
+
+
+ 涨
+
+
+
+ 跌
+
+
+
+ 平
+
+ |
+ 点击色块查看下级分类
+
+
+ {/* 热力图网格 */}
+
+ {currentData.map((item, index) => (
+
+ ))}
+
{/* 统计信息 */}
- 当前显示 {currentData.length} 个{currentLevel === 'lv1' ? '一级' : currentLevel === 'lv2' ? '二级' : '三级'}分类
+ 当前显示 {currentData.length} 个
+ {currentLevel === 'lv1' ? '一级' : currentLevel === 'lv2' ? '二级' : '三级'}分类
{currentLevel === 'lv1' && (
- <>
-
- 共 {hierarchy.reduce((acc, h) => acc + (h.children?.length || 0), 0)} 个二级分类
-
-
- 共 {hierarchy.reduce((acc, h) => acc + h.concept_count, 0)} 个概念
-
- >
+
+ 共 {hierarchy.reduce((acc, h) => acc + h.concept_count, 0)} 个概念
+
)}