态势感知平台-开源生态与贡献价值全景-开发者活力波形图组件抽离
This commit is contained in:
371
src/views/Jyh/ecosystem/components/VitalityWaveChart.vue
Normal file
371
src/views/Jyh/ecosystem/components/VitalityWaveChart.vue
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
<template>
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, nextTick, watch } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||||
|
|
||||||
|
// 页面尺寸响应式
|
||||||
|
const { widthType } = usePageResize();
|
||||||
|
|
||||||
|
// 开发者活力数据
|
||||||
|
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]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图表实例存储(用于resize时销毁重绘)
|
||||||
|
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
|
||||||
|
vitalityChart: null
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化开发者活力波形图
|
||||||
|
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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面resize时重绘图表
|
||||||
|
watch(widthType, () => {
|
||||||
|
nextTick(() => initVitalityChart());
|
||||||
|
});
|
||||||
|
|
||||||
|
// 挂载时初始化
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
initVitalityChart();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式适配 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.vitality__container {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vitality__title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.vitality__container {
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vitality__title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -186,18 +186,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 开发者活力波形图 -->
|
<!-- 开发者活力波形图 -->
|
||||||
<div class="index-module__NV_5cW__vitalitySection">
|
<VitalityWaveChart />
|
||||||
<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>
|
|
||||||
<!-- 开源项目热力图 -->
|
<!-- 开源项目热力图 -->
|
||||||
<div class="index-module__NV_5cW__vitalitySection heatmap-section">
|
<div class="index-module__NV_5cW__vitalitySection heatmap-section">
|
||||||
<div class="heatmap-header">
|
<div class="heatmap-header">
|
||||||
@@ -276,6 +265,7 @@ import TalentMapChart from './TalentMapChart.vue'
|
|||||||
import InfrastructureCoverageChart from './components/InfrastructureCoverageChart.vue'
|
import InfrastructureCoverageChart from './components/InfrastructureCoverageChart.vue'
|
||||||
import UniversityClubStatsChart from './components/UniversityClubStatsChart.vue'
|
import UniversityClubStatsChart from './components/UniversityClubStatsChart.vue'
|
||||||
import CommunityGovernanceHealthChart from './components/CommunityGovernanceHealthChart.vue'
|
import CommunityGovernanceHealthChart from './components/CommunityGovernanceHealthChart.vue'
|
||||||
|
import VitalityWaveChart from './components/VitalityWaveChart.vue'
|
||||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||||
|
|
||||||
// 页面尺寸响应式
|
// 页面尺寸响应式
|
||||||
@@ -477,12 +467,7 @@ const isEmpty = ref({
|
|||||||
// 语言图表Tab状态
|
// 语言图表Tab状态
|
||||||
const languageTab = ref<'mainstream' | 'emerging'>('mainstream');
|
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, 活跃仓库数, 活跃开发者数, 项目名称, 所属组织]
|
// 开源项目热力图数据 [OpenRank, 活跃仓库数, 活跃开发者数, 项目名称, 所属组织]
|
||||||
const heatmapData = ref([
|
const heatmapData = ref([
|
||||||
@@ -539,7 +524,6 @@ const getTalentLevel = (density: number) => {
|
|||||||
// 图表实例存储(用于resize时销毁重绘)
|
// 图表实例存储(用于resize时销毁重绘)
|
||||||
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
|
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
|
||||||
languageTechChart: null,
|
languageTechChart: null,
|
||||||
vitalityChart: null,
|
|
||||||
heatmapChart: null
|
heatmapChart: null
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -987,232 +971,7 @@ const changeLanguageTab = () => {
|
|||||||
initLanguageTechChart();
|
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 = () => {
|
const initHeatmapChart = () => {
|
||||||
@@ -1514,7 +1273,6 @@ const initCharts = () => {
|
|||||||
|
|
||||||
// 初始化各图表
|
// 初始化各图表
|
||||||
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
|
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
|
||||||
nextTick(() => initVitalityChart());
|
|
||||||
nextTick(() => initHeatmapChart());
|
nextTick(() => initHeatmapChart());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user