态势感知平台-开源生态与贡献价值全景-开发者活力波形图组件抽离

This commit is contained in:
d266377
2026-01-28 11:20:44 +08:00
parent 3ffe0716e6
commit 03a099ea10
2 changed files with 374 additions and 245 deletions

View File

@@ -186,18 +186,7 @@
</div>
<!-- 开发者活力波形图 -->
<div class="index-module__NV_5cW__vitalitySection">
<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="M22 12h-4l-3 9L9 3l-3 9H2"></path>
</svg>
<span>开发者活力波形图</span>
</h3>
<div class="vitality__container">
<div id="vitalityChart" class="chart"></div>
</div>
</div>
<VitalityWaveChart />
<!-- 开源项目热力图 -->
<div class="index-module__NV_5cW__vitalitySection heatmap-section">
<div class="heatmap-header">
@@ -276,6 +265,7 @@ 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 { usePageResize } from '@/utils/hooks/usePageResize';
// 页面尺寸响应式
@@ -477,12 +467,7 @@ const isEmpty = ref({
// 语言图表Tab状态
const languageTab = ref<'mainstream' | 'emerging'>('mainstream');
// 开发者活力数据
const vitalityData = ref({
timeData: ["2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024"],
total: [20, 50, 100, 200, 300, 400, 600, 800, 1000, 1200, 1350],
new: [20, 30, 50, 100, 100, 100, 200, 200, 200, 200, 150]
});
// 开源项目热力图数据 [OpenRank, 活跃仓库数, 活跃开发者数, 项目名称, 所属组织]
const heatmapData = ref([
@@ -539,7 +524,6 @@ const getTalentLevel = (density: number) => {
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
languageTechChart: null,
vitalityChart: null,
heatmapChart: null
});
@@ -987,232 +971,7 @@ const changeLanguageTab = () => {
initLanguageTechChart();
};
// 初始化开发者活力波形图
const initVitalityChart = () => {
const el = document.getElementById('vitalityChart');
if (!el) return;
if (chartInstances.value.vitalityChart) {
chartInstances.value.vitalityChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.vitalityChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999',
width: 1,
type: 'dashed'
},
lineStyle: {
color: '#3b82f6',
width: 1,
type: 'dashed'
}
},
backgroundColor: 'rgba(255, 255, 255, 0.98)',
borderColor: '#10b981',
borderWidth: 2,
borderRadius: 8,
padding: 16,
textStyle: { color: '#333', fontSize: 14 },
extraCssText: 'box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);',
formatter: (params: any) => {
if (!Array.isArray(params) || params.length === 0) {
return '<div>暂无数据</div>';
}
const totalItem = params.find((item: any) => item.seriesName === '累计开发者');
const newItem = params.find((item: any) => item.seriesName === '年度新增');
const totalValue = totalItem?.value || 0;
const newValue = newItem?.value || 0;
const time = totalItem?.axisValue || newItem?.axisValue || '未知时间';
// 计算增长率
const prevIndex = vitalityData.value.timeData.indexOf(time) - 1;
let growthRate = 0;
if (prevIndex >= 0) {
const prevTotal = vitalityData.value.total[prevIndex];
growthRate = ((totalValue - prevTotal) / prevTotal * 100).toFixed(1);
}
return `
<div style="line-height: 2;">
<div style="font-weight: bold; color: #10b981; font-size: 16px; margin-bottom: 8px; border-bottom: 2px solid #10b981; padding-bottom: 4px;">📅 ${time}年</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="display: inline-block; width: 8px; height: 8px; background: #10b981; border-radius: 50%;"></span>
累计开发者:<span style="color: #10b981; font-weight: 700; font-size: 15px;">${totalValue}</span> 万人
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="display: inline-block; width: 8px; height: 8px; background: #f59e0b; border-radius: 50%;"></span>
年度新增:<span style="color: #f59e0b; font-weight: 700; font-size: 15px;">${newValue}</span> 万人
</div>
${growthRate > 0 ? `<div style="margin-top: 6px; padding: 4px 8px; background: rgba(16, 185, 129, 0.1); border-radius: 4px; color: #10b981; font-size: 12px;">📈 同比增长 ${growthRate}%</div>` : ''}
</div>
`;
}
},
legend: {
left: 'center',
top: '0%',
itemWidth: 16,
itemHeight: 16,
itemGap: 20,
textStyle: { color: '#333', fontSize: 14, fontWeight: '600' },
data: ['累计开发者', '年度新增'],
icon: 'roundRect',
borderRadius: 4
},
grid: {
top: '18%',
left: '3%',
right: '4%',
bottom: '8%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
axisTick: { show: false },
axisLine: {
lineStyle: { color: '#e5e7eb', width: 2 },
onZero: false
},
axisLabel: {
color: '#666',
fontSize: 13,
fontWeight: '500',
interval: 0,
margin: 12
},
data: vitalityData.value.timeData
},
yAxis: {
type: 'value',
name: '开发者数量(万人)',
nameTextStyle: { color: '#666', fontSize: 12 },
axisLabel: { color: '#666', fontSize: 12 },
splitLine: {
show: true,
lineStyle: { color: 'rgba(0, 0, 0, 0.06)', type: 'dashed' }
}
},
series: [
{
name: '累计开发者',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 8,
lineStyle: { color: '#10b981', width: 3 },
itemStyle: {
color: '#10b981',
borderWidth: 2,
borderColor: '#fff'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(16, 185, 129, 0.4)' },
{ offset: 1, color: 'rgba(16, 185, 129, 0.05)' }
]),
shadowBlur: 10,
shadowColor: 'rgba(16, 185, 129, 0.2)'
},
data: vitalityData.value.total,
emphasis: {
itemStyle: {
shadowBlur: 20,
shadowColor: 'rgba(16, 185, 129, 0.6)',
scale: 1.2
},
label: {
show: true,
formatter: '{c} 万',
position: 'top',
color: '#10b981',
fontSize: 13,
fontWeight: 'bold'
}
},
markPoint: {
data: [
{
type: 'max', name: '最大值',
itemStyle: { color: '#10b981' },
label: {
color: '#fff',
fontSize: 12,
fontWeight: 'bold'
}
}
],
symbolSize: 60
},
markLine: {
silent: true,
lineStyle: {
color: '#10b981',
type: 'dashed',
width: 1
},
label: {
color: '#10b981',
fontSize: 11
},
data: [{ type: 'average', name: '平均值' }]
}
},
{
name: '年度新增',
type: 'bar',
barWidth: '40%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#f59e0b' },
{ offset: 1, color: '#fbbf24' }
]),
borderRadius: [4, 4, 0, 0],
shadowBlur: 8,
shadowColor: 'rgba(245, 158, 11, 0.3)'
},
data: vitalityData.value.new,
emphasis: {
itemStyle: {
shadowBlur: 20,
shadowColor: 'rgba(245, 158, 11, 0.6)'
},
label: {
show: true,
formatter: '{c} 万',
position: 'top',
color: '#f59e0b',
fontSize: 12,
fontWeight: 'bold'
}
},
markPoint: {
data: [
{
type: 'max', name: '峰值',
itemStyle: { color: '#f59e0b' },
label: {
color: '#fff',
fontSize: 12,
fontWeight: 'bold'
}
}
],
symbolSize: 60
},
animationDelay: (idx: number) => idx * 50
}
]
});
};
// 初始化开源项目热力图
const initHeatmapChart = () => {
@@ -1514,7 +1273,6 @@ const initCharts = () => {
// 初始化各图表
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
nextTick(() => initVitalityChart());
nextTick(() => initHeatmapChart());
};