Files
Situation-Awareness-Platfor…/src/views/Jyh/ecosystem/index.vue

2975 lines
74 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="index-module__NV_5cW__container">
<!-- 头部区域标题副标题武汉核心数据统计 -->
<div class="index-module__NV_5cW__header">
<div class="index-module__NV_5cW__floatingElements">
<div class="index-module__NV_5cW__floatingElement"></div>
<div class="index-module__NV_5cW__floatingElement"></div>
<div class="index-module__NV_5cW__floatingElement"></div>
</div>
<div class="index-module__NV_5cW__titleSection">
<!-- <div class="index-module__NV_5cW__titleContainer">-->
<!-- <h1 class="index-module__NV_5cW__title">开源生态与贡献价值全景</h1>-->
<!-- </div>-->
<!-- <div class="index-module__NV_5cW__subtitle">-->
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"-->
<!-- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"-->
<!-- class="lucide lucide-users" aria-hidden="true">-->
<!-- <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>-->
<!-- <path d="M16 3.128a4 4 0 0 1 0 7.744"></path>-->
<!-- <path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>-->
<!-- <circle cx="9" cy="7" r="4"></circle>-->
<!-- </svg>-->
<!-- <span>汇聚武汉开源力量共建城市开源生态</span>-->
<!-- </div>-->
<!-- 武汉4项核心开源数据统计 -->
<StatsGlass />
</div>
</div>
<!-- 内容区域ECharts图表模块 + 项目列表 -->
<div class="index-module__NV_5cW__content">
<!-- ECharts图表区域 -->
<div class="index-module__NV_5cW__chartsSection">
<!-- <div class="index-module__NV_5cW__sectionHeader">-->
<!-- <h2 class="index-module__NV_5cW__sectionTitle">-->
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"-->
<!-- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"-->
<!-- class="lucide lucide-chart-pie" aria-hidden="true">-->
<!-- <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>-->
<!-- <circle cx="12" cy="10" r="3"></circle>-->
<!-- </svg>-->
<!-- <span>开源生态与贡献价值全景数据可视化</span>-->
<!-- </h2>-->
<!-- </div>-->
<!-- 图表网格布局新增多个模块 -->
<div class="index-module__NV_5cW__chartsGrid">
<!-- 1. 开源企业贡献榜-->
<div class="index-module__NV_5cW__chartCard list-card">
<div class="chartCard__header">
<div class="title-with-stats">
<h3 class="chartCard__title">全球开源企业贡献榜 TOP 30</h3>
<div class="enterprise-stats" v-if="enterpriseFilter !== 'all'">
<span class="stat-badge">
平均分: {{ avgScore }}
</span>
<span class="stat-badge">
总仓库: {{ totalRepos }}
</span>
</div>
</div>
<div class="enterprise-filter">
<span :class="['filter-tag', { active: enterpriseFilter === 'all' }]"
@click="changeEnterpriseFilter('all')">
全部 ({{ allEnterprises.length }})
</span>
<span :class="['filter-tag', { active: enterpriseFilter === 'China' }]"
@click="changeEnterpriseFilter('China')">
🇨🇳 中国 ({{ chinaCount }})
</span>
<span :class="['filter-tag', { active: enterpriseFilter === 'USA' }]"
@click="changeEnterpriseFilter('USA')">
🇺🇸 美国 ({{ usaCount }})
</span>
</div>
</div>
<div class="chartCard__container list-container" ref="scrollContainer1" @mouseenter="pauseScroll(1)"
@mouseleave="resumeScroll(1)">
<div class="scroll-indicator">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M12 5v14M19 12l-7 7-7-7" />
</svg>
<span>悬停暂停滚动</span>
</div>
<div class="project-list enterprise-list">
<div class="project-item enterprise-item" v-for="(item, index) in excellentProjects" :key="index"
:title="`${item.name}\n全球排名: #${item.rank}\n综合得分: ${item.score}\n仓库数: ${item.repos}\n开发者: ${item.devs}\n国家: ${item.country}`">
<span :class="['enterprise-rank', getRankClass(item.displayRank || item.rank)]">
{{ getRankText(item.displayRank || item.rank) }}
</span>
<div class="enterprise-info">
<span class="enterprise-name">{{ item.name }}</span>
<div class="enterprise-meta">
<span class="meta-item">{{ getCountryFlag(item.country) }}</span>
<span class="meta-item">{{ item.repos }} 仓库</span>
<span class="meta-item meta-devs">{{ formatDevs(item.devs) }} 开发者</span>
</div>
</div>
<div class="score-bar-container">
<div :class="['score-bar', getScoreBarClass(item.score)]"
:style="{ width: (30 + (item.score - 60) / 36 * 70) + '%' }">
</div>
</div>
<span :class="['enterprise-score', getScoreClass(item.score)]">
{{ item.score }}
</span>
</div>
</div>
</div>
</div>
<!-- 2. 基础设施覆盖率多层环形图-->
<InfrastructureCoverageChart />
<!-- 3. 高校开源社团/俱乐部数量-->
<UniversityClubStatsChart />
<!-- 4. 社区治理健康度对比-->
<CommunityGovernanceHealthChart />
<!-- 5. 语言与技术竞争-->
<div class="index-module__NV_5cW__chartCard">
<div class="chartCard__header">
<h3 class="chartCard__title">语言与技术竞争</h3>
<div class="language-tab" @click="changeLanguageTab">
<span :class="{ active: languageTab === 'mainstream' }">主流语言</span>
<span :class="{ active: languageTab === 'emerging' }">新兴语言</span>
</div>
</div>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.languageTech"></div>
<div v-else id="languageTechChart" class="chart"></div>
</div>
</div>
<!-- 6. 明星开发者聚焦列表-->
<StarDevelopersChart />
</div>
</div>
<!-- 开发者活力波形图 -->
<VitalityWaveChart />
<!-- 开源项目热力图 -->
<div class="index-module__NV_5cW__vitalitySection heatmap-section">
<div class="heatmap-header">
<div class="title-row">
<h3 class="vitality__title">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z">
</path>
</svg>
<span>开源项目热力图</span>
</h3>
<div class="label-toggle" @click="toggleLabels">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2v20M2 12h20"></path>
</svg>
<span>{{ showAllLabels ? '隐藏标签' : '显示全部' }}</span>
</div>
</div>
<div class="heatmap-stats">
<div class="stat-item">
<span class="stat-value">{{ heatmapData.length }}</span>
<span class="stat-label">项目总数</span>
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<span class="stat-value">{{ topProject.name }}</span>
<span class="stat-label">领先项目</span>
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<span class="stat-value">{{ totalDevs }}</span>
<span class="stat-label">总开发者数</span>
</div>
</div>
</div>
<div class="vitality__container heatmap-container">
<div class="heatmap-legend">
<div class="legend-item">
<div class="legend-icon bubble-small"></div>
<span>少量开发者</span>
</div>
<div class="legend-item">
<div class="legend-icon bubble-medium"></div>
<span>中等规模</span>
</div>
<div class="legend-item">
<div class="legend-icon bubble-large"></div>
<span>大量开发者</span>
</div>
</div>
<div id="heatmapChart" class="chart"></div>
</div>
</div>
<!-- 中国开源人才密度地图 -->
<div class="index-module__NV_5cW__vitalitySection map-section">
<h3 class="vitality__title">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
<span>🌍 中国开源人才密度地图</span>
</h3>
<TalentMapChart />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, watch, onUnmounted, computed } from 'vue';
import * as echarts from 'echarts';
import TalentMapChart from './TalentMapChart.vue'
import InfrastructureCoverageChart from './components/InfrastructureCoverageChart.vue'
import UniversityClubStatsChart from './components/UniversityClubStatsChart.vue'
import CommunityGovernanceHealthChart from './components/CommunityGovernanceHealthChart.vue'
import VitalityWaveChart from './components/VitalityWaveChart.vue'
import StatsGlass from './components/StatsGlass.vue'
import StarDevelopersChart from './components/StarDevelopersChart.vue'
import { usePageResize } from '@/utils/hooks/usePageResize';
// 页面尺寸响应式
const { widthType } = usePageResize();
// 1. 全球开源企业贡献榜数据(来自 EnterpriseContributionRanking.vue
const rawEnterpriseData = [
{ name: 'Microsoft', openrank: 177654.43, repos: 9211, devs: 14954, country: 'USA' },
{ name: 'Huawei', openrank: 108690.66, repos: 11407, devs: 16413, country: 'China' },
{ name: 'Google', openrank: 64156.6, repos: 3426, devs: 64115, country: 'USA' },
{ name: 'Amazon', openrank: 54776.3, repos: 7850, devs: 35802, country: 'USA' },
{ name: 'Meta', openrank: 34811.3, repos: 1410, devs: 31912, country: 'USA' },
{ name: 'RedHat', openrank: 28004.37, repos: 736, devs: 3626, country: 'USA' },
{ name: 'Mozilla', openrank: 27085.14, repos: 1792, devs: 17242, country: 'USA' },
{ name: 'Alibaba', openrank: 24744.15, repos: 5570, devs: 18661, country: 'China' },
{ name: 'IBM', openrank: 21165.28, repos: 626, devs: 5658, country: 'USA' },
{ name: 'Elastic', openrank: 21472.45, repos: 2437, devs: 17735, country: 'USA' },
{ name: 'Nabu Casa', openrank: 18499.45, repos: 101, devs: 27413, country: 'USA' },
{ name: 'Grafana Labs', openrank: 18237.03, repos: 756, devs: 10925, country: 'USA' },
{ name: 'Ant Group', openrank: 17916.45, repos: 1251, devs: 13760, country: 'China' },
{ name: 'DataDog', openrank: 17795.41, repos: 935, devs: 5401, country: 'USA' },
{ name: 'Intel', openrank: 17725.74, repos: 1046, devs: 6142, country: 'USA' },
{ name: 'HashiCorp', openrank: 17229.81, repos: 745, devs: 17068, country: 'USA' },
{ name: 'Hugging Face', openrank: 16909.25, repos: 277, devs: 15021, country: 'USA' },
{ name: 'NVIDIA', openrank: 16082.9, repos: 850, devs: 12362, country: 'USA' },
{ name: 'Baidu', openrank: 15695.3, repos: 408, devs: 9386, country: 'China' },
{ name: 'VMWare', openrank: 14627.44, repos: 634, devs: 11794, country: 'USA' },
{ name: 'Odoo', openrank: 12959.59, repos: 27, devs: 2678, country: 'Belgium' },
{ name: 'Dio Academia', openrank: 11231.13, repos: 169, devs: 24070, country: 'Portugal' },
{ name: 'Oracle', openrank: 10799.13, repos: 528, devs: 37959, country: 'USA' },
{ name: 'Yandex', openrank: 10025.79, repos: 283, devs: 10112, country: 'Germany' },
{ name: 'NextCloud', openrank: 10029.67, repos: 229, devs: 39516, country: 'Russia' },
{ name: 'ByteDance', openrank: 8310.37, repos: 652, devs: 5989, country: 'China' },
{ name: 'WordPress', openrank: 8267.62, repos: 176, devs: 777, country: 'USA' },
{ name: 'Vercel', openrank: 8214.69, repos: 170, devs: 13420, country: 'USA' },
{ name: 'LangChain', openrank: 8108.64, repos: 135, devs: 10099, country: 'USA' },
{ name: 'ConsenSys', openrank: 8071.43, repos: 640, devs: 4686, country: 'USA' }
];
// 计算综合得分并排序
const calculateEnterpriseScore = () => {
const maxOpenrank = Math.max(...rawEnterpriseData.map(d => d.openrank));
const maxRepos = Math.max(...rawEnterpriseData.map(d => d.repos));
const maxDevs = Math.max(...rawEnterpriseData.map(d => d.devs));
const scored = rawEnterpriseData.map(item => {
const openrankScore = Math.sqrt(item.openrank / maxOpenrank) * 100;
const reposScore = Math.sqrt(item.repos / maxRepos) * 100;
const devsScore = Math.sqrt(item.devs / maxDevs) * 100;
const compositeScore = openrankScore * 0.4 + reposScore * 0.25 + devsScore * 0.35;
return { ...item, rawScore: compositeScore };
});
const sorted = scored.sort((a, b) => b.rawScore - a.rawScore);
const maxScore = sorted[0].rawScore;
const minScore = sorted[sorted.length - 1].rawScore;
return sorted.map((item, index) => {
let normalizedScore = ((item.rawScore - minScore) / (maxScore - minScore)) * 36 + 60;
normalizedScore = Math.max(60, Math.min(96, normalizedScore - index * 0.15));
return {
rank: index + 1,
name: item.name,
score: Math.round(normalizedScore * 10) / 10,
repos: item.repos,
country: item.country,
devs: item.devs
};
});
};
const allEnterprises = calculateEnterpriseScore();
const enterpriseFilter = ref<'all' | 'China' | 'USA'>('all');
// 计算各国企业数量
const chinaCount = computed(() => allEnterprises.filter(item => item.country === 'China').length);
const usaCount = computed(() => allEnterprises.filter(item => item.country === 'USA').length);
// 切换筛选并重启滚动
const changeEnterpriseFilter = (filter: 'all' | 'China' | 'USA') => {
enterpriseFilter.value = filter;
// 重启滚动
nextTick(() => {
pauseScroll(1);
setTimeout(() => autoScroll(1), 100);
});
};
// 计算筛选后的统计数据
const avgScore = computed(() => {
if (enterpriseFilter.value === 'all') return 0;
const filtered = excellentProjects.value;
if (filtered.length === 0) return 0;
const sum = filtered.reduce((acc, item) => acc + item.score, 0);
return (sum / filtered.length).toFixed(1);
});
const totalRepos = computed(() => {
if (enterpriseFilter.value === 'all') return 0;
return excellentProjects.value.reduce((acc, item) => acc + item.repos, 0).toLocaleString();
});
// 根据筛选条件过滤企业数据
const excellentProjects = computed(() => {
if (enterpriseFilter.value === 'all') {
return allEnterprises;
}
const filtered = allEnterprises.filter(item => item.country === enterpriseFilter.value);
// 重新计算排名
return filtered.map((item, index) => ({
...item,
displayRank: index + 1
}));
});
// 3. 图表数据定义
const chartData = ref({
// 基础设施覆盖率(来自 InfrastructureCoverageRings.vue
infrastructure: [
{
name: '镜像站规模',
value: 85,
color: '#00F0FF',
radius: ['70%', '80%']
},
{
name: '代码托管平台规模',
value: 90,
color: '#FFAB00',
radius: ['50%', '60%']
},
{
name: '构建平台活跃度',
value: 40,
color: '#B20003',
radius: ['30%', '40%']
}
],
// 语言与技术竞争(南丁格尔玫瑰图数据)
languageTech: {
// 主流语言数据基于2025年数据增长率为2024→2025的变化
mainstream: [
{ name: 'JavaScript', value: 61, growth: 0 },
{ name: 'Python', value: 57, growth: 0 },
{ name: 'HTML/CSS', value: 52, growth: 1.96 },
{ name: 'Java', value: 49, growth: 6.52 },
{ name: 'SQL', value: 47, growth: -2.08 },
{ name: 'Shell', value: 36, growth: 0 }
],
// 新兴语言数据基于2025年数据增长率为2024→2025的变化
emerging: [
{ name: 'TypeScript', value: 42, growth: 13.51 },
{ name: 'C++', value: 25, growth: 0 },
{ name: 'C#', value: 21, growth: -4.55 },
{ name: 'Go', value: 20, growth: 11.11 },
{ name: 'Kotlin', value: 18, growth: 28.57 },
{ name: 'C', value: 18, growth: 0 }
]
}
});
// 空数据标识
const isEmpty = ref({
languageTech: false
});
// 语言图表Tab状态
const languageTab = ref<'mainstream' | 'emerging'>('mainstream');
// 开源项目热力图数据 [OpenRank, 活跃仓库数, 活跃开发者数, 项目名称, 所属组织]
const heatmapData = ref([
[67538.71, 29433, 1409, 'OpenHarmony', 'Huawei'],
[55000, 15000, 6000, 'Linux', 'Linux Foundation'],
[60000, 8000, 7000, 'Android', 'Google'],
[52000, 20000, 5500, 'Chromium', 'Google'],
[38000, 16000, 4500, 'TensorFlow', 'Google'],
[42000, 13000, 5000, 'React', 'Meta'],
[45417.67, 18000, 8629, 'Azure', 'Microsoft'],
[35000, 22000, 5000, 'Kubernetes', 'CNCF'],
[21844.62, 12340, 57, 'LLM', '发芽工具'],
[25241.13, 8000, 83, 'NixOS', '-'],
[22961.09, 4500, 9377, 'openEuler', 'Huawei'],
[17795.41, 2792, 975, 'DataDog', 'DataDog'],
[25598.21, 2082, 282, '.Net', '操作系统'],
[7405.46, 9639, 3, 'Expensify', 'Expensify'],
[8862.2, 4625, 120, 'PaddlePaddle', 'Baidu'],
[12959.59, 2363, 1, 'Odoo', 'Odoo'],
[12326.76, 881, 179, 'Rust', 'Rust Foundation'],
[6778.7, 400, 479, 'CloudHarmony', '企业应用']
]);
// 计算热力图统计数据
const topProject = computed(() => {
const sorted = [...heatmapData.value].sort((a, b) => b[0] - a[0]);
return { name: sorted[0][3], openrank: sorted[0][0] };
});
const totalDevs = computed(() => {
const total = heatmapData.value.reduce((sum, item) => sum + item[2], 0);
if (total >= 10000) {
return (total / 10000).toFixed(1) + '万';
}
return total.toLocaleString();
});
// 标签显示控制(默认显示全部)
const showAllLabels = ref(true);
const toggleLabels = () => {
showAllLabels.value = !showAllLabels.value;
initHeatmapChart();
};
// 获取人才等级
const getTalentLevel = (density: number) => {
if (density >= 500) return '🔥 核心枢纽';
if (density >= 100) return '⭐ 重要聚集地';
if (density >= 10) return '📈 潜力增长区';
return '🌱 发展中区域';
};
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
languageTechChart: null,
heatmapChart: null
});
// ===================== 新增:列表自动滚动逻辑 =====================
// 1. 定义滚动容器的ref
const scrollContainer1 = ref<HTMLDivElement | null>(null); // 第一个列表容器
const scrollContainer2 = ref<HTMLDivElement | null>(null); // 第二个列表容器
// 2. 存储定时器1/2定时器ID
const scrollTimers: { [key: number]: NodeJS.Timeout | null } = {
1: null,
2: null
};
// 3. 滚动配置
const scrollStep = 1; // 每次滚动的步长(像素,越小越流畅)
const scrollInterval = 20; // 滚动间隔(毫秒,越小越流畅)
// 4. 通用自动滚动函数
const autoScroll = (containerKey: number) => {
// 获取对应容器
const container = containerKey === 1 ? scrollContainer1.value : scrollContainer2.value;
if (!container) return;
// 获取列表内容元素project-list
const projectList = container.querySelector('.project-list') as HTMLDivElement;
if (!projectList) return;
// 若内容高度 ≤ 容器高度,不执行滚动
if (projectList.scrollHeight <= container.clientHeight) {
// 清除现有定时器
if (scrollTimers[containerKey]) {
clearInterval(scrollTimers[containerKey]!);
scrollTimers[containerKey] = null;
}
return;
}
// 清除现有定时器(避免重复)
if (scrollTimers[containerKey]) {
clearInterval(scrollTimers[containerKey]!);
}
// 启动定时器滚动
scrollTimers[containerKey] = setInterval(() => {
// 向下滚动步长
container.scrollTop += scrollStep;
// 滚动到底部时重置为0循环滚动
// 加1是为了兼容像素精度问题
if (projectList.scrollHeight - container.scrollTop <= container.clientHeight + 1) {
container.scrollTop = 0; // 直接重置也可以用平滑滚动container.scrollTo({ top: 0, behavior: 'smooth' })
}
}, scrollInterval);
};
// 5. 暂停滚动
const pauseScroll = (containerKey: number) => {
if (scrollTimers[containerKey]) {
clearInterval(scrollTimers[containerKey]!);
scrollTimers[containerKey] = null;
}
};
// 6. 恢复滚动
const resumeScroll = (containerKey: number) => {
autoScroll(containerKey);
};
// 7. 组件卸载时清除所有定时器
onUnmounted(() => {
Object.keys(scrollTimers).forEach(key => {
const k = Number(key);
if (scrollTimers[k]) {
clearInterval(scrollTimers[k]!);
scrollTimers[k] = null;
}
});
});
// 10. 企业排名相关方法
const getRankClass = (rank: number) => {
if (rank === 1) return 'gold';
if (rank === 2) return 'silver';
if (rank === 3) return 'bronze';
return 'normal';
};
const getRankText = (rank: number) => {
if (rank <= 3) return rank.toString();
return rank.toString().padStart(2, '0');
};
const getScoreClass = (score: number) => {
if (score >= 90) return 'score-blue'; // 90+ 蓝色
if (score >= 80) return 'score-light-blue'; // 80-89 浅蓝色
if (score >= 70) return 'score-green'; // 70-79 绿色
return 'score-red'; // 60-69 红色
};
const getScoreBarClass = (score: number) => {
if (score >= 90) return 'bar-blue'; // 90+ 蓝色
if (score >= 80) return 'bar-light-blue'; // 80-89 浅蓝色
if (score >= 70) return 'bar-green'; // 70-79 绿色
return 'bar-red'; // 60-69 红色
};
const getCountryFlag = (country: string) => {
const countryMap: { [key: string]: string } = {
'China': '🇨🇳',
'USA': '🇺🇸',
'Belgium': '🇧🇪',
'Portugal': '🇵🇹',
'Germany': '🇩🇪',
'Russia': '🇷🇺'
};
return countryMap[country] || country;
};
const formatDevs = (num: number) => {
if (num >= 10000) {
return (num / 1000).toFixed(1) + 'K';
}
if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toString();
};
// ===================== 新增结束 =====================
// 根据增长率动态生成颜色的函数
const getColorByGrowth = (growth: number, tab: string) => {
const minGrowth = -5;
const maxGrowth = tab === 'mainstream' ? 10 : 30;
const normalized = Math.max(0, Math.min(1, (growth - minGrowth) / (maxGrowth - minGrowth)));
const colorStops = [
{ pos: 0, color: '#0063D0' },
{ pos: 0.2, color: '#0081FF' },
{ pos: 0.4, color: '#00A0FF' },
{ pos: 0.6, color: '#00C0FF' },
{ pos: 0.8, color: '#00E0FF' },
{ pos: 1, color: '#00F2FF' }
];
let startColor = colorStops[0].color;
let endColor = colorStops[0].color;
let localPos = 0;
for (let i = 0; i < colorStops.length - 1; i++) {
if (normalized >= colorStops[i].pos && normalized <= colorStops[i + 1].pos) {
startColor = colorStops[i].color;
endColor = colorStops[i + 1].color;
localPos = (normalized - colorStops[i].pos) / (colorStops[i + 1].pos - colorStops[i].pos);
break;
}
}
const interpolateColor = (color1: string, color2: string, factor: number) => {
const c1 = parseInt(color1.slice(1), 16);
const c2 = parseInt(color2.slice(1), 16);
const r1 = (c1 >> 16) & 0xff, g1 = (c1 >> 8) & 0xff, b1 = c1 & 0xff;
const r2 = (c2 >> 16) & 0xff, g2 = (c2 >> 8) & 0xff, b2 = c2 & 0xff;
const r = Math.round(r1 + (r2 - r1) * factor);
const g = Math.round(g1 + (g2 - g1) * factor);
const b = Math.round(b1 + (b2 - b1) * factor);
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
};
const topColor = interpolateColor(startColor, endColor, localPos);
return new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: topColor },
{ offset: 1, color: startColor }
]);
};
// 初始化语言与技术竞争(南丁格尔玫瑰图)
const initLanguageTechChart = () => {
const el = document.getElementById('languageTechChart');
if (!el) return;
if (chartInstances.value.languageTechChart) {
chartInstances.value.languageTechChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.languageTechChart = myChart;
const currentData = languageTab.value === 'mainstream'
? chartData.value.languageTech.mainstream
: chartData.value.languageTech.emerging;
myChart.setOption({
graphic: {
elements: [
// 中心发光圆
{
type: 'circle',
z: 0,
shape: { cx: '48%', cy: '54%', r: '21%' },
style: {
fill: {
type: 'radial',
x: 0.5,
y: 0.5,
r: 0.5,
colorStops: [
{ offset: 0, color: 'rgba(59, 130, 246, 0.15)' },
{ offset: 0.7, color: 'rgba(59, 130, 246, 0.05)' },
{ offset: 1, color: 'rgba(59, 130, 246, 0)' }
]
},
stroke: 'rgba(59, 130, 246, 0.4)',
lineWidth: 2,
shadowBlur: 10,
shadowColor: 'rgba(59, 130, 246, 0.3)'
}
},
// 中间虚线圆
{
type: 'circle',
z: 0,
shape: { cx: '48%', cy: '54%', r: '50%' },
style: {
stroke: 'rgba(139, 92, 246, 0.2)',
lineWidth: 1.5,
lineDash: [8, 4]
}
},
// 外层虚线圆
{
type: 'circle',
z: 0,
shape: { cx: '48%', cy: '54%', r: '80%' },
style: {
stroke: 'rgba(236, 72, 153, 0.15)',
lineWidth: 1,
lineDash: [5, 5]
}
},
// 装饰性小圆点12点钟方向
{
type: 'circle',
z: 1,
shape: { cx: '48%', cy: '12%', r: 4 },
style: {
fill: '#3b82f6',
shadowBlur: 8,
shadowColor: 'rgba(59, 130, 246, 0.6)'
}
},
// 装饰性小圆点3点钟方向
{
type: 'circle',
z: 1,
shape: { cx: '88%', cy: '54%', r: 3 },
style: {
fill: '#8b5cf6',
shadowBlur: 6,
shadowColor: 'rgba(139, 92, 246, 0.6)'
}
},
// 装饰性小圆点6点钟方向
{
type: 'circle',
z: 1,
shape: { cx: '48%', cy: '96%', r: 3 },
style: {
fill: '#ec4899',
shadowBlur: 6,
shadowColor: 'rgba(236, 72, 153, 0.6)'
}
},
// 装饰性小圆点9点钟方向
{
type: 'circle',
z: 1,
shape: { cx: '8%', cy: '54%', r: 3 },
style: {
fill: '#10b981',
shadowBlur: 6,
shadowColor: 'rgba(16, 185, 129, 0.6)'
}
}
]
},
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#3b82f6',
borderWidth: 1,
borderRadius: 6,
padding: 10,
textStyle: { color: '#333', fontSize: 13 },
formatter: (params: any) => `
<div style="line-height: 1.8;">
<div style="font-weight: bold; color: #3b82f6;">${params.name}</div>
<div>市场占比:<span style="color: #10b981;">${params.value}%</span></div>
<div>增长率:<span style="color: #f59e0b;">${params.data.growth}%</span></div>
</div>
`
},
visualMap: {
show: true,
type: 'continuous',
min: -5,
max: languageTab.value === 'mainstream' ? 10 : 30,
dimension: 2,
orient: 'vertical',
itemWidth: 14,
itemHeight: 90,
text: ['高增长', '低增长'],
textStyle: { color: '#333', fontSize: 11, fontWeight: '600' },
inRange: { color: ['#0063D0', '#00F2FF'] },
right: '2%',
top: '0%',
borderColor: 'rgba(59, 130, 246, 0.3)',
borderWidth: 2,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
padding: 8,
borderRadius: 8,
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.1)'
},
polar: {
radius: ['25%', '78%'],
center: ['48%', '54%'],
axisLine: { show: false },
splitLine: {
show: true,
lineStyle: { color: 'rgba(0,0,0,0.05)', type: 'dashed' }
}
},
angleAxis: {
type: 'category',
data: currentData.map(item => item.name),
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
color: '#333',
fontSize: 13,
fontWeight: '500',
margin: 18,
rotate: 0
}
},
radiusAxis: {
type: 'value',
min: 0,
max: Math.max(...currentData.map(item => item.value)) * 1.1,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false },
splitLine: {
show: true,
lineStyle: { color: 'rgba(0,0,0,0.08)', type: 'dashed' }
}
},
series: [
{
name: '语言占比',
type: 'bar',
coordinateSystem: 'polar',
data: currentData.map((item, index) => {
const baseColor = getColorByGrowth(item.growth, languageTab.value);
// 为每个花瓣添加独特的渐变效果
const gradientColor = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: baseColor.colorStops[0].color },
{ offset: 0.5, color: baseColor.colorStops[0].color },
{ offset: 1, color: baseColor.colorStops[1].color }
]);
return {
value: item.value,
growth: item.growth,
itemStyle: {
color: gradientColor,
borderRadius: [18, 18, 0, 0],
borderWidth: 2,
borderColor: 'rgba(255, 255, 255, 0.4)',
shadowBlur: 12,
shadowColor: 'rgba(59, 130, 246, 0.4)',
opacity: 0.95
}
};
}),
barWidth: '35%',
barGap: '-15%',
label: {
show: true,
position: 'inside',
color: '#FFFFFF',
fontSize: 13,
fontWeight: 'bold',
formatter: '{c}%',
distance: 5
},
emphasis: {
itemStyle: {
shadowBlur: 25,
shadowColor: 'rgba(59, 130, 246, 0.8)',
borderWidth: 3,
borderColor: 'rgba(255, 255, 255, 0.6)',
opacity: 1
},
label: {
fontSize: 15,
fontWeight: 'bold',
color: '#FFFFFF',
textShadowBlur: 4,
textShadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
// 添加动画效果
animationType: 'scale',
animationEasing: 'elasticOut',
animationDelay: (idx: number) => idx * 100
}
]
});
};
// 切换语言Tab
const changeLanguageTab = () => {
languageTab.value = languageTab.value === 'mainstream' ? 'emerging' : 'mainstream';
initLanguageTechChart();
};
// 初始化开源项目热力图
const initHeatmapChart = () => {
const el = document.getElementById('heatmapChart');
if (!el) return;
if (chartInstances.value.heatmapChart) {
chartInstances.value.heatmapChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.heatmapChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(255, 255, 255, 0.98)',
borderColor: '#3b82f6',
borderWidth: 2,
borderRadius: 10,
padding: 18,
textStyle: { color: '#333', fontSize: 13 },
extraCssText: 'box-shadow: 0 8px 24px rgba(59, 130, 246, 0.25);',
formatter: (params: any) => {
const data = params.data.value;
const name = data[3];
const org = data[4];
const openrank = data[0].toLocaleString();
const repos = data[1].toLocaleString();
const devs = data[2].toLocaleString();
// 计算排名
const sorted = [...heatmapData.value].sort((a, b) => b[0] - a[0]);
const rank = sorted.findIndex(item => item[3] === name) + 1;
return `
<div style="line-height: 2.2;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
<div style="font-weight: bold; color: #3b82f6; font-size: 16px;">${name}</div>
<div style="background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: #fff; padding: 2px 10px; border-radius: 12px; font-size: 11px; font-weight: 600;">TOP ${rank}</div>
</div>
<div style="border-bottom: 2px solid #3b82f6; margin-bottom: 10px; opacity: 0.3;"></div>
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 6px;">
<span style="display: inline-block; width: 10px; height: 10px; background: linear-gradient(135deg, #f59e0b, #fbbf24); border-radius: 50%; box-shadow: 0 2px 4px rgba(245, 158, 11, 0.3);"></span>
<span style="color: #666; min-width: 80px;">所属组织</span>
<span style="color: #f59e0b; font-weight: 700;">${org}</span>
</div>
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 6px;">
<span style="display: inline-block; width: 10px; height: 10px; background: linear-gradient(135deg, #3b82f6, #60a5fa); border-radius: 50%; box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);"></span>
<span style="color: #666; min-width: 80px;">OpenRank</span>
<span style="color: #3b82f6; font-weight: 700; font-size: 14px;">${openrank}</span>
</div>
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 6px;">
<span style="display: inline-block; width: 10px; height: 10px; background: linear-gradient(135deg, #8b5cf6, #a78bfa); border-radius: 50%; box-shadow: 0 2px 4px rgba(139, 92, 246, 0.3);"></span>
<span style="color: #666; min-width: 80px;">活跃仓库</span>
<span style="color: #8b5cf6; font-weight: 700; font-size: 14px;">${repos}</span>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<span style="display: inline-block; width: 10px; height: 10px; background: linear-gradient(135deg, #10b981, #34d399); border-radius: 50%; box-shadow: 0 2px 4px rgba(16, 185, 129, 0.3);"></span>
<span style="color: #666; min-width: 80px;">活跃开发者</span>
<span style="color: #10b981; font-weight: 700; font-size: 14px;">${devs}</span>
</div>
</div>
`;
}
},
visualMap: {
min: 0,
max: 9000,
dimension: 2,
orient: 'vertical',
right: '2%',
top: 'center',
itemWidth: 18,
itemHeight: 140,
text: ['🔥 热门', '❄️ 冷门'],
calculable: true,
inRange: {
color: ['#FF4900', '#FFAB00', '#00B03E', '#00C0FF', '#00F0FF']
},
textStyle: {
color: '#333',
fontSize: 13,
fontWeight: '600'
},
borderColor: 'rgba(59, 130, 246, 0.3)',
borderWidth: 2,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
padding: 12,
borderRadius: 8,
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.1)'
},
grid: {
top: '8%',
left: '8%',
right: '16%',
bottom: '10%',
containLabel: true
},
xAxis: {
type: 'value',
name: 'OpenRank 影响力指数',
nameLocation: 'center',
nameGap: 35,
min: 1000,
max: 70000,
nameTextStyle: {
color: '#3b82f6',
fontSize: 14,
fontWeight: '700'
},
axisLine: {
lineStyle: {
color: '#3b82f6',
width: 2,
shadowBlur: 4,
shadowColor: 'rgba(59, 130, 246, 0.3)'
}
},
axisLabel: {
color: '#666',
fontSize: 12,
fontWeight: '600',
formatter: (value: number) => {
if (value >= 1000) {
return (value / 1000).toFixed(0) + 'k';
}
return value;
}
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(59, 130, 246, 0.1)',
type: 'dashed',
width: 1
}
}
},
yAxis: {
type: 'value',
name: '活跃仓库数量',
nameLocation: 'center',
nameGap: 45,
min: 0,
max: 30000,
nameTextStyle: {
color: '#8b5cf6',
fontSize: 14,
fontWeight: '700'
},
axisLine: {
lineStyle: {
color: '#8b5cf6',
width: 2,
shadowBlur: 4,
shadowColor: 'rgba(139, 92, 246, 0.3)'
}
},
axisLabel: {
color: '#666',
fontSize: 12,
fontWeight: '600',
formatter: (value: number) => {
if (value >= 1000) {
return (value / 1000).toFixed(0) + 'k';
}
return value;
}
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(139, 92, 246, 0.1)',
type: 'dashed',
width: 1
}
}
},
series: [
{
name: '开源项目',
type: 'scatter',
data: heatmapData.value.map(item => ({
value: item,
name: item[3]
})),
symbolSize: (data: any) => {
const devs = data[2];
return Math.log(devs + 1) * 4 + 10;
},
itemStyle: {
opacity: 0.9,
shadowBlur: 15,
shadowColor: 'rgba(59, 130, 246, 0.5)',
borderWidth: 2.5,
borderColor: 'rgba(255, 255, 255, 0.8)'
},
label: {
show: true,
formatter: (params: any) => {
const openrank = params.value[0];
const repos = params.value[1];
const devs = params.value[2];
// 如果开启显示全部,则显示所有标签
if (showAllLabels.value) {
return params.value[3];
}
// 否则只显示重要项目OpenRank > 15000 或 开发者数 > 3000 或 仓库数 > 10000
if (openrank > 15000 || devs > 3000 || repos > 10000) {
return params.value[3];
}
return '';
},
position: (pos: any, params: any, dom: any, rect: any, size: any) => {
// 智能定位:根据位置自动调整标签方向,避免重叠
const x = pos[0];
const y = pos[1];
const viewWidth = size.viewSize[0];
const viewHeight = size.viewSize[1];
// 右上区域:标签在上方
if (x > viewWidth * 0.6 && y < viewHeight * 0.4) {
return 'top';
}
// 右下区域:标签在右侧
if (x > viewWidth * 0.6 && y > viewHeight * 0.6) {
return 'right';
}
// 左上区域:标签在左侧
if (x < viewWidth * 0.4 && y < viewHeight * 0.4) {
return 'left';
}
// 左下区域:标签在下方
if (x < viewWidth * 0.4 && y > viewHeight * 0.6) {
return 'bottom';
}
// 默认在上方
return 'top';
},
color: '#1e293b',
fontSize: 10,
fontWeight: 'bold',
backgroundColor: 'rgba(255, 255, 255, 0.95)',
padding: [3, 7],
borderRadius: 5,
borderWidth: 1.5,
borderColor: 'rgba(59, 130, 246, 0.4)',
shadowBlur: 6,
shadowColor: 'rgba(0, 0, 0, 0.1)',
distance: 8
},
labelLayout: {
// 标签避让配置
hideOverlap: true,
moveOverlap: 'shiftY',
draggable: false
},
emphasis: {
focus: 'self',
itemStyle: {
shadowBlur: 25,
shadowColor: 'rgba(59, 130, 246, 0.9)',
borderWidth: 4,
borderColor: '#fff',
opacity: 1
},
label: {
show: true,
fontSize: 12,
fontWeight: 'bold',
backgroundColor: 'rgba(59, 130, 246, 0.98)',
color: '#fff',
borderColor: '#3b82f6',
borderWidth: 2,
padding: [5, 9],
shadowBlur: 10,
shadowColor: 'rgba(59, 130, 246, 0.5)',
distance: 10
}
},
animationType: 'scale',
animationEasing: 'elasticOut',
animationDelay: (idx: number) => idx * 40
}
]
});
};
// 统一初始化图表
const initCharts = () => {
// 空数据判断
isEmpty.value = {
languageTech: chartData.value.languageTech.mainstream.length === 0 && chartData.value.languageTech.emerging.length === 0
};
// 初始化各图表
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
nextTick(() => initHeatmapChart());
};
// 页面resize时重绘图表
watch(widthType, () => {
initCharts();
// 重新启动滚动防止resize后滚动失效
autoScroll(1);
autoScroll(2);
});
// 挂载时初始化
onMounted(() => {
setTimeout(() => {
initCharts();
// 启动列表自动滚动
autoScroll(1);
autoScroll(2);
}, 100);
});
</script>
<style lang="scss" scoped>
.index-module__NV_5cW__container {
//background: linear-gradient(to bottom right, #faf5ff, #60a5fa);
min-height: 100vh;
padding: 2rem 1rem;
position: relative;
overflow-x: hidden;
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.index-module__NV_5cW__header {
width: 1400px;
text-align: center;
padding: 1rem;
position: relative;
margin: 0 auto 3rem;
}
.index-module__NV_5cW__floatingElements {
pointer-events: none;
opacity: 0.3;
position: absolute;
inset: 0;
overflow: hidden;
}
.index-module__NV_5cW__floatingElement {
background: linear-gradient(135deg, #3b82f60d, #9333ea0d);
border-radius: 50%;
width: 120px;
height: 120px;
animation: 30s linear infinite index-module__NV_5cW__float;
position: absolute;
}
.index-module__NV_5cW__floatingElement:first-child {
animation-delay: 0s;
top: 10%;
left: 10%;
}
.index-module__NV_5cW__floatingElement:nth-child(2) {
animation-delay: -7s;
top: 60%;
right: 10%;
}
.index-module__NV_5cW__floatingElement:nth-child(3) {
animation-delay: -14s;
bottom: 20%;
left: 50%;
}
@keyframes index-module__NV_5cW__float {
0% {
transform: translateY(0) rotate(0);
}
33% {
transform: translateY(-20px) rotate(120deg);
}
66% {
transform: translateY(10px) rotate(240deg);
}
to {
transform: translateY(0) rotate(360deg);
}
}
.index-module__NV_5cW__titleSection {
color: #333;
z-index: 2;
flex-direction: column;
align-items: center;
gap: 1rem;
display: flex;
position: relative;
}
.index-module__NV_5cW__titleContainer {
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 1rem;
display: flex;
}
.index-module__NV_5cW__title {
background: #333;
-webkit-text-fill-color: transparent;
letter-spacing: -0.02em;
white-space: nowrap;
-webkit-background-clip: text;
background-clip: text;
margin: 0;
font-size: 2.8rem;
font-weight: 700;
}
.index-module__NV_5cW__titleBadge {
color: #fff;
letter-spacing: 0.05em;
white-space: nowrap;
background: #60a5fa;
border-radius: 1.5rem;
padding: 0.4rem 1rem;
font-size: 1rem;
font-weight: 600;
box-shadow: 0 2px 10px #3b82f64d;
}
.index-module__NV_5cW__subtitle {
color: #666;
text-align: center;
align-items: center;
gap: 0.5rem;
max-width: 500px;
font-size: 1.1rem;
font-weight: 400;
display: flex;
}
.index-module__NV_5cW__statsBar {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffff1a;
border: 1px solid #fff3;
border-radius: 1.5rem;
align-items: center;
gap: 1.5rem;
width: fit-content;
max-width: 90%;
margin-top: 0.75rem;
padding: 0.75rem 1.5rem;
display: flex;
}
.index-module__NV_5cW__stat {
flex-direction: column;
align-items: center;
gap: 0.25rem;
display: flex;
}
.index-module__NV_5cW__statNumber {
color: #60a5fa;
font-size: 1.5rem;
font-weight: 700;
}
.index-module__NV_5cW__statLabel {
color: #333;
font-size: 0.875rem;
font-weight: 500;
}
.index-module__NV_5cW__statDivider {
background: #fff3;
width: 1px;
height: 2rem;
}
.index-module__NV_5cW__content {
max-width: 1400px;
/* 加宽容器,适配更多图表 */
margin: 0 auto;
padding: 0 1rem;
}
/* 图表区域样式适配示例的num-empty */
.index-module__NV_5cW__chartsSection {
margin-bottom: 3rem;
}
.index-module__NV_5cW__chartsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
/* 调整列宽,适配列表 */
gap: 1.5rem;
padding: 0;
}
.index-module__NV_5cW__chartCard {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffffe6;
border: 1px solid #ffffff4d;
border-radius: 1rem;
padding: 1.5rem;
box-shadow: 0 4px 16px #00000014;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.index-module__NV_5cW__chartCard::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.05), transparent);
transition: left 0.6s;
}
.index-module__NV_5cW__chartCard:hover::before {
left: 100%;
}
/* 列表卡片特殊样式 */
.index-module__NV_5cW__chartCard.list-card {
padding: 1.5rem;
}
.index-module__NV_5cW__chartCard:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.index-module__NV_5cW__chartCard:hover .chartCard__title {
color: #3b82f6;
transform: scale(1.02);
transition: all 0.3s;
}
.chartCard__wide {
grid-column: 1 / -1;
/* 跨列显示 */
}
.chartCard__header {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 1rem;
gap: 0.75rem;
}
.title-with-stats {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
width: 100%;
}
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0;
text-align: center;
width: 100%;
}
.enterprise-stats {
display: flex;
gap: 0.5rem;
align-items: center;
animation: fadeInDown 0.5s ease-out;
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.stat-badge {
padding: 0.25rem 0.75rem;
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
border-radius: 1rem;
font-size: 0.75rem;
font-weight: 600;
color: #3b82f6;
white-space: nowrap;
}
.language-tab {
display: flex;
gap: 0.5rem;
background: linear-gradient(135deg, #f0f4ff, #faf5ff);
border-radius: 1rem;
padding: 0.3rem;
cursor: pointer;
user-select: none;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.1);
transition: all 0.3s;
}
.language-tab:hover {
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
border-color: rgba(59, 130, 246, 0.2);
}
.language-tab span {
padding: 0.45rem 1.2rem;
border-radius: 0.75rem;
font-size: 0.875rem;
font-weight: 600;
color: #666;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.language-tab span::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.language-tab span:hover::before {
width: 100%;
height: 100%;
}
.language-tab span.active {
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
color: #fff;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
transform: translateY(-1px);
}
.language-tab span.active::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), transparent);
border-radius: 0.75rem;
}
/* 企业筛选器样式 */
.enterprise-filter {
display: flex;
gap: 0.4rem;
background: linear-gradient(135deg, #f0f4ff, #faf5ff);
border-radius: 1rem;
padding: 0.25rem;
cursor: pointer;
user-select: none;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.1);
transition: all 0.3s;
}
.enterprise-filter:hover {
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
border-color: rgba(59, 130, 246, 0.2);
}
.filter-tag {
padding: 0.35rem 0.9rem;
border-radius: 0.75rem;
font-size: 0.8rem;
font-weight: 600;
color: #666;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
cursor: pointer;
}
.filter-tag::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.filter-tag:hover::before {
width: 100%;
height: 100%;
}
.filter-tag.active {
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
color: #fff;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
transform: translateY(-1px);
}
.filter-tag.active::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), transparent);
border-radius: 0.75rem;
}
.chartCard__container {
width: 100%;
height: 390px;
position: relative;
overflow: hidden;
}
/* 为语言图表添加特殊背景 */
.chartCard__wide .chartCard__container {
background: linear-gradient(135deg,
rgba(59, 130, 246, 0.02) 0%,
rgba(139, 92, 246, 0.02) 50%,
rgba(236, 72, 153, 0.02) 100%);
border-radius: 0.5rem;
}
/* 列表容器样式:隐藏原生滚动条(可选,视觉更美观) */
.list-container {
height: auto;
max-height: 300px;
/* 限制列表高度,超出滚动 */
overflow-y: auto;
padding: 0 0.5rem;
/* 隐藏滚动条 */
scrollbar-width: none;
/* Firefox */
-ms-overflow-style: none;
/* IE/Edge */
position: relative;
}
/* 隐藏webkit内核浏览器的滚动条 */
.chartCard__container.list-container::-webkit-scrollbar {
display: none;
}
/* 滚动指示器 */
.scroll-indicator {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
background: rgba(59, 130, 246, 0.1);
border-radius: 0 0 0 0.5rem;
font-size: 0.7rem;
color: #3b82f6;
z-index: 10;
animation: bounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(3px);
}
}
.scroll-indicator svg {
width: 12px;
height: 12px;
}
/* 项目列表样式 */
.project-list {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.project-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.8rem;
background: #f8f9fa;
border-radius: 0.5rem;
border-left: 3px solid #2951E0;
transition: all 0.2s;
}
/* 鼠标悬停项目项时的样式(可选) */
.project-item:hover {
background: #f0f4ff;
transform: translateX(2px);
}
.project-rank {
width: 24px;
height: 24px;
border-radius: 50%;
background: #2951E0;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
.project-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.project-name {
font-size: 14px;
font-weight: 600;
color: #333;
}
.project-desc {
font-size: 12px;
color: #666;
font-style: italic;
}
.project-org {
font-size: 12px;
color: #888;
flex-shrink: 0;
white-space: nowrap;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
}
/* 企业列表特殊样式 */
.enterprise-list {
gap: 0.6rem;
}
.enterprise-item {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-left: 3px solid #3b82f6;
padding: 0.75rem 1rem;
gap: 0.75rem;
align-items: center;
position: relative;
overflow: hidden;
animation: slideInRight 0.3s ease-out backwards;
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.enterprise-item:nth-child(1) {
animation-delay: 0.05s;
}
.enterprise-item:nth-child(2) {
animation-delay: 0.1s;
}
.enterprise-item:nth-child(3) {
animation-delay: 0.15s;
}
.enterprise-item:nth-child(4) {
animation-delay: 0.2s;
}
.enterprise-item:nth-child(5) {
animation-delay: 0.25s;
}
.enterprise-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.1), transparent);
transition: left 0.5s;
}
.enterprise-item:hover::before {
left: 100%;
}
.enterprise-item:hover {
background: linear-gradient(135deg, #f0f4ff 0%, #eff6ff 100%);
border-left-color: #2563eb;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.15);
transform: translateX(3px);
}
.enterprise-rank {
width: 32px;
height: 24px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
flex-shrink: 0;
font-family: 'DIN Alternate', 'Arial', sans-serif;
}
.enterprise-rank.gold {
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
color: #2c3e50;
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.4);
}
.enterprise-rank.silver {
background: linear-gradient(135deg, #E8E8E8 0%, #C0C0C0 100%);
color: #2c3e50;
box-shadow: 0 2px 6px rgba(192, 192, 192, 0.4);
}
.enterprise-rank.bronze {
background: linear-gradient(135deg, #CD7F32 0%, #B8733C 100%);
color: #fff;
box-shadow: 0 2px 6px rgba(205, 127, 50, 0.4);
}
.enterprise-rank.normal {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: #fff;
}
.enterprise-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.3rem;
min-width: 0;
}
.enterprise-name {
font-size: 14px;
font-weight: 700;
color: #1e293b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.enterprise-meta {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 11px;
color: #64748b;
}
.meta-item {
display: flex;
align-items: center;
gap: 2px;
}
.meta-devs {
color: #8b5cf6;
font-weight: 600;
}
.score-bar-container {
width: 80px;
height: 6px;
background: rgba(0, 0, 0, 0.08);
border-radius: 3px;
overflow: hidden;
flex-shrink: 0;
}
.score-bar {
height: 100%;
border-radius: 3px;
transition: width 0.5s ease-out;
}
.score-bar.bar-blue {
background: linear-gradient(90deg, #2563eb 0%, #3b82f6 100%);
box-shadow: 0 0 4px rgba(37, 99, 235, 0.5);
}
.score-bar.bar-light-blue {
background: linear-gradient(90deg, #60a5fa 0%, #93c5fd 100%);
box-shadow: 0 0 4px rgba(96, 165, 250, 0.4);
}
.score-bar.bar-green {
background: linear-gradient(90deg, #10b981 0%, #34d399 100%);
box-shadow: 0 0 4px rgba(16, 185, 129, 0.4);
}
.score-bar.bar-red {
background: linear-gradient(90deg, #ef4444 0%, #f87171 100%);
box-shadow: 0 0 4px rgba(239, 68, 68, 0.4);
}
.enterprise-score {
font-family: 'DIN Alternate', 'Arial', sans-serif;
font-size: 15px;
font-weight: 700;
width: 40px;
text-align: right;
flex-shrink: 0;
}
.enterprise-score.score-blue {
color: #2563eb;
text-shadow: 0 0 8px rgba(37, 99, 235, 0.3);
}
.enterprise-score.score-light-blue {
color: #60a5fa;
text-shadow: 0 0 6px rgba(96, 165, 250, 0.3);
}
.enterprise-score.score-green {
color: #10b981;
text-shadow: 0 0 6px rgba(16, 185, 129, 0.2);
}
.enterprise-score.score-red {
color: #ef4444;
text-shadow: 0 0 6px rgba(239, 68, 68, 0.3);
}
/* 明星开发者特殊样式 */
.developer-list-container {
max-height: 400px;
margin-top: 20px;
}
.developer-item {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border-left: 3px solid #3b82f6;
padding: 0.75rem 1rem;
gap: 0.75rem;
}
.developer-item:hover {
background: linear-gradient(135deg, #eff6ff 0%, #f0f4ff 100%);
border-left-color: #8b5cf6;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.15);
}
.dev-avatar {
width: 42px;
height: 42px;
border-radius: 50%;
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
overflow: hidden;
flex-shrink: 0;
}
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-text {
font-size: 16px;
font-weight: bold;
color: #fff;
}
.dev-rank {
width: auto;
height: auto;
border-radius: 12px;
background: linear-gradient(135deg, #ec4899 0%, #f59e0b 100%);
padding: 3px 10px;
font-size: 11px;
font-weight: 700;
box-shadow: 0 2px 6px rgba(236, 72, 153, 0.3);
}
.dev-info {
flex: 1;
gap: 0.3rem;
}
.dev-name {
font-size: 15px;
font-weight: 700;
color: #1e293b;
}
.dev-location {
font-size: 11px;
color: #64748b;
font-style: normal;
}
.dev-followers {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 12px;
background: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.2);
color: #3b82f6;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
max-width: none;
}
.dev-followers svg {
flex-shrink: 0;
}
/* 与示例一致的空数据样式 */
.num-empty {
height: 10px;
border-top: 2px solid #2951E0;
width: 100%;
background: linear-gradient(180deg, rgba(41, 81, 224, 0.12) 0%, rgba(41, 81, 224, 0.04) 100%);
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.chart {
width: 100%;
height: 100%;
}
.index-module__NV_5cW__sectionHeader {
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
display: flex;
position: relative;
}
.index-module__NV_5cW__sectionTitle {
color: #333;
text-align: center;
justify-content: center;
align-items: center;
gap: 0.75rem;
width: 100%;
margin: 0;
font-size: 1.5rem;
font-weight: 600;
display: flex;
}
.index-module__NV_5cW__vitalitySection {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffffe6;
border: 1px solid #ffffff4d;
border-radius: 1.5rem;
margin-bottom: 2rem;
padding: 2rem;
box-shadow: 0 8px 24px #00000014;
transition: all 0.3s;
}
.index-module__NV_5cW__vitalitySection:hover {
border-color: #3b82f64d;
transform: translateY(-2px);
box-shadow: 0 12px 32px #0000001f;
}
.vitality__title {
color: #333;
font-size: 1.5rem;
font-weight: 600;
margin: 0 0 1.5rem 0;
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
position: relative;
}
.vitality__title::before {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background: linear-gradient(90deg, #10b981, #f59e0b);
border-radius: 2px;
}
.vitality__title svg {
color: #10b981;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.vitality__container {
width: 100%;
height: 400px;
position: relative;
background: linear-gradient(135deg,
rgba(16, 185, 129, 0.02) 0%,
rgba(245, 158, 11, 0.02) 100%);
border-radius: 0.75rem;
padding: 1rem;
}
/* 地图区域样式(科技蓝风格) */
.map-section {
position: relative;
overflow: hidden;
border: 2px solid rgba(0, 192, 255, 0.1);
box-shadow: 0 4px 16px rgba(0, 192, 255, 0.1);
}
.map-section::before {
content: '';
position: absolute;
top: -30%;
left: -30%;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(0, 192, 255, 0.1) 0%, transparent 70%);
animation: rotate 30s linear infinite;
pointer-events: none;
}
/* 热力图特殊样式 */
.heatmap-section {
position: relative;
overflow: hidden;
}
.heatmap-section::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(59, 130, 246, 0.1) 0%, transparent 70%);
animation: rotate 20s linear infinite;
pointer-events: none;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.heatmap-header {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
}
.label-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1));
border: 2px solid rgba(59, 130, 246, 0.3);
border-radius: 1.5rem;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
font-size: 0.85rem;
font-weight: 600;
color: #3b82f6;
}
.label-toggle:hover {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(139, 92, 246, 0.2));
border-color: rgba(59, 130, 246, 0.5);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.label-toggle:active {
transform: translateY(0);
}
.label-toggle svg {
width: 16px;
height: 16px;
transition: transform 0.3s;
}
.label-toggle:hover svg {
transform: rotate(90deg);
}
.heatmap-stats {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 1rem 2rem;
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.9) 0%,
rgba(255, 255, 255, 0.7) 100%);
border-radius: 1rem;
border: 2px solid rgba(59, 130, 246, 0.2);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.3rem;
}
.stat-value {
font-size: 1.3rem;
font-weight: 700;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.stat-label {
font-size: 0.8rem;
color: #666;
font-weight: 500;
}
.stat-divider {
width: 2px;
height: 2.5rem;
background: linear-gradient(to bottom,
transparent 0%,
rgba(59, 130, 246, 0.3) 50%,
transparent 100%);
}
.heatmap-container {
background: linear-gradient(135deg,
rgba(59, 130, 246, 0.02) 0%,
rgba(139, 92, 246, 0.02) 50%,
rgba(236, 72, 153, 0.02) 100%);
border: 1px solid rgba(59, 130, 246, 0.1);
position: relative;
}
.heatmap-legend {
position: absolute;
top: 1rem;
left: 1rem;
display: flex;
gap: 1.5rem;
padding: 0.75rem 1.25rem;
background: rgba(255, 255, 255, 0.95);
border-radius: 1rem;
border: 2px solid rgba(59, 130, 246, 0.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 10;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.legend-item {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.8rem;
color: #666;
font-weight: 600;
}
.legend-icon {
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.8);
box-shadow: 0 2px 6px rgba(59, 130, 246, 0.3);
}
.bubble-small {
width: 12px;
height: 12px;
background: linear-gradient(135deg, #FF4900, #FFAB00);
}
.bubble-medium {
width: 18px;
height: 18px;
background: linear-gradient(135deg, #00B03E, #00C0FF);
}
.bubble-large {
width: 24px;
height: 24px;
background: linear-gradient(135deg, #00C0FF, #00F0FF);
}
.index-module__NV_5cW__actionSection {
text-align: center;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffffe6;
border: 1px solid #ffffff4d;
border-radius: 1.5rem;
margin: 0;
padding: 2.5rem 2rem;
position: relative;
overflow: hidden;
box-shadow: 0 8px 24px #00000014;
}
.index-module__NV_5cW__actionBackground {
z-index: 1;
background: linear-gradient(135deg, #3b82f60d 0%, #9333ea0d 100%);
position: absolute;
inset: 0;
}
.index-module__NV_5cW__actionContent {
z-index: 2;
max-width: 600px;
margin: 0 auto;
position: relative;
}
.index-module__NV_5cW__actionIcon {
color: #fff;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
border-radius: 50%;
justify-content: center;
align-items: center;
width: 3rem;
height: 3rem;
margin: 0 auto 1.5rem;
display: flex;
box-shadow: 0 4px 12px #3b82f633;
}
.index-module__NV_5cW__actionTitle {
background: linear-gradient(135deg, #1e293b 0%, #3b82f6 100%);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-clip: text;
margin-bottom: 1rem;
font-size: 2rem;
font-weight: 700;
line-height: 1.2;
}
.index-module__NV_5cW__actionDescription {
color: #64748b;
margin-bottom: 2rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
}
.index-module__NV_5cW__actionFeatures {
flex-wrap: wrap;
justify-content: center;
gap: 1.5rem;
margin-bottom: 2rem;
display: flex;
}
.index-module__NV_5cW__feature {
color: #3b82f6;
background: #3b82f614;
border: 1px solid #3b82f626;
border-radius: 1.5rem;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 1rem;
font-size: 0.85rem;
font-weight: 500;
display: flex;
}
.index-module__NV_5cW__createButton {
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
border: none;
border-radius: 1.5rem;
align-items: center;
gap: 0.5rem;
height: auto;
margin: 0 auto;
padding: 0.75rem 2rem;
font-size: 1rem;
font-weight: 600;
transition: all 0.3s;
display: flex;
box-shadow: 0 4px 12px #3b82f64d;
}
.index-module__NV_5cW__createButton:hover {
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
transform: translateY(-2px);
box-shadow: 0 6px 18px #3b82f666;
}
/* 响应式适配 */
@media (max-width: 768px) {
.index-module__NV_5cW__titleContainer {
text-align: center;
flex-direction: column;
gap: 0.5rem;
}
.index-module__NV_5cW__title {
font-size: 2.5rem;
}
.index-module__NV_5cW__titleBadge {
padding: 0.4rem 1.2rem;
font-size: 1rem;
}
.index-module__NV_5cW__container {
padding: 1rem;
}
.index-module__NV_5cW__header {
margin-bottom: 2rem;
padding: 1.5rem 1rem;
}
.index-module__NV_5cW__statsBar {
flex-wrap: nowrap;
justify-content: space-between;
gap: 0.8rem;
width: 100%;
max-width: 100%;
padding: 0.8rem 1.2rem;
}
.index-module__NV_5cW__stat {
text-align: center;
flex: 1;
min-width: 60px;
}
.index-module__NV_5cW__statNumber {
font-size: 1.1rem;
}
.index-module__NV_5cW__statLabel {
white-space: nowrap;
font-size: 0.7rem;
}
.index-module__NV_5cW__statDivider {
width: 1px;
height: 1.2rem;
}
.index-module__NV_5cW__sectionTitle {
text-align: left;
flex: 1;
justify-content: flex-start;
gap: 0.5rem;
font-size: 1.3rem;
}
.index-module__NV_5cW__actionSection {
border-radius: 1rem;
margin: 0;
padding: 2rem 1.5rem;
}
.index-module__NV_5cW__actionTitle {
font-size: 1.6rem;
}
.index-module__NV_5cW__actionFeatures {
flex-direction: column;
align-items: center;
gap: 1rem;
}
.index-module__NV_5cW__createButton {
padding: 0.65rem 1.5rem;
font-size: 0.95rem;
}
.chartCard__container {
height: 250px;
}
.vitality__container {
height: 300px;
}
.heatmap-stats {
flex-wrap: wrap;
gap: 1rem;
padding: 0.8rem 1rem;
}
.stat-item {
flex: 1;
min-width: 80px;
}
.stat-value {
font-size: 1.1rem;
}
.stat-label {
font-size: 0.7rem;
}
.stat-divider {
height: 2rem;
}
.title-row {
flex-direction: column;
align-items: flex-start;
}
.label-toggle {
padding: 0.4rem 0.8rem;
font-size: 0.75rem;
}
.heatmap-legend {
flex-direction: column;
gap: 0.75rem;
padding: 0.6rem 0.9rem;
top: 0.5rem;
left: 0.5rem;
}
.legend-item {
font-size: 0.7rem;
}
.bubble-small {
width: 10px;
height: 10px;
}
.bubble-medium {
width: 14px;
height: 14px;
}
.bubble-large {
width: 18px;
height: 18px;
}
/* 地图移动端适配 */
.map-container {
flex-direction: column;
gap: 1rem;
}
.map-container .chart {
min-height: 350px;
}
.map-rank-panel {
width: 100%;
}
.rank-item {
padding: 0.6rem 0.8rem;
gap: 0.5rem;
}
.rank-num {
width: 28px;
height: 28px;
font-size: 12px;
}
.rank-name {
font-size: 13px;
}
.rank-score {
font-size: 13px;
}
/* 移动端列表样式调整 */
.project-item {
flex-wrap: wrap;
gap: 0.5rem;
}
.project-org {
width: 100%;
max-width: 100%;
margin-top: 0.2rem;
}
/* 企业列表移动端调整 */
.enterprise-item {
padding: 0.6rem 0.8rem;
gap: 0.5rem;
}
.enterprise-rank {
width: 28px;
height: 22px;
font-size: 12px;
}
.enterprise-name {
font-size: 13px;
}
.enterprise-meta {
font-size: 10px;
}
.score-bar-container {
width: 60px;
}
.enterprise-score {
font-size: 13px;
width: 35px;
}
}
@media (max-width: 480px) {
.index-module__NV_5cW__title {
font-size: 1.8rem;
}
.index-module__NV_5cW__actionSection {
padding: 1.5rem 1rem;
}
.index-module__NV_5cW__statsBar {
gap: 0.6rem;
padding: 0.6rem 0.8rem;
}
.index-module__NV_5cW__statNumber {
font-size: 1rem;
}
.index-module__NV_5cW__statLabel {
font-size: 0.65rem;
}
.index-module__NV_5cW__sectionTitle {
gap: 0.4rem;
font-size: 1.2rem;
}
.chartCard__container {
height: 200px;
}
.vitality__container {
height: 250px;
}
.heatmap-stats {
gap: 0.6rem;
padding: 0.6rem 0.8rem;
}
.stat-value {
font-size: 1rem;
}
.stat-label {
font-size: 0.65rem;
}
.heatmap-legend {
gap: 0.5rem;
padding: 0.5rem 0.7rem;
}
.legend-item {
font-size: 0.65rem;
gap: 0.3rem;
}
.vitality__title {
font-size: 1.2rem;
}
.index-module__NV_5cW__chartsGrid {
grid-template-columns: 1fr;
}
/* 地图小屏适配 */
.map-container .chart {
min-height: 300px;
padding: 0.5rem;
}
.map-rank-panel {
padding: 1rem;
}
.panel-title {
font-size: 1rem;
padding-bottom: 0.75rem;
}
.rank-item {
padding: 0.5rem 0.6rem;
}
.rank-num {
width: 26px;
height: 26px;
font-size: 11px;
}
.rank-name {
font-size: 12px;
}
.rank-score {
fo
/* 小屏列表高度调整 */
.chartCard__container.list-container {
max-height: 300px;
}
/* 企业列表小屏调整 */
.enterprise-item {
padding: 0.5rem 0.6rem;
}
.enterprise-rank {
width: 26px;
height: 20px;
font-size: 11px;
}
.enterprise-name {
font-size: 12px;
}
.score-bar-container {
width: 50px;
height: 5px;
}
.enterprise-score {
font-size: 12px;
width: 32px;
}
}
}
.stats-glass-bar {
display: flex;
align-items: center;
justify-content: space-between;
/* 布局控制:撑满容器并增加内边距 */
width: 100%;
padding: 2rem 4rem;
//margin: 2rem 0;
/* 深色玻璃拟态背景 */
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
/* 极细微的边框勾勒 */
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.08);
/* 外阴影与内发光,增加悬浮感 */
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3),
inset 0 0 20px rgba(255, 255, 255, 0.02);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: rgba(56, 189, 248, 0.4);
background: rgba(255, 255, 255, 0.04);
transform: translateY(-2px);
}
}
.stat-node {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
transition: transform 0.3s ease;
&:hover {
transform: scale(1.05);
.stat-value {
text-shadow: 0 0 25px rgba(56, 189, 248, 0.8);
}
}
}
.stat-value {
/* 科技感亮蓝渐变 */
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 2.25rem;
font-weight: 800;
background: linear-gradient(180deg, #fff 0%, #38bdf8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
/* 基础发光效果 */
filter: drop-shadow(0 0 10px rgba(56, 189, 248, 0.3));
letter-spacing: -1px;
}
.stat-desc {
/* 在黑底上使用半透明白色,更有高级感 */
color: rgba(255, 255, 255, 0.6);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.1rem;
text-transform: uppercase;
}
.stat-separator {
/* 渐隐分割线 */
width: 1px;
height: 3rem;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0.2),
rgba(255, 255, 255, 0)
);
margin: 0 1rem;
}
/* 响应式适配 */
@media (max-width: 1024px) {
.stats-glass-bar {
padding: 2rem;
}
.stat-value {
font-size: 1.75rem;
}
}
@media (max-width: 768px) {
.stats-glass-bar {
flex-direction: column;
gap: 2rem;
}
.stat-separator {
width: 50%;
height: 1px;
background: linear-gradient(to right, transparent, rgba(255,255,255,0.2), transparent);
}
}
</style>