2975 lines
74 KiB
Vue
2975 lines
74 KiB
Vue
<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>
|