态势感知平台-开源生态与贡献价值全景高校开源社区/俱乐部数量组件抽离
This commit is contained in:
364
src/views/Jyh/ecosystem/components/UniversityClubStatsChart.vue
Normal file
364
src/views/Jyh/ecosystem/components/UniversityClubStatsChart.vue
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
<template>
|
||||||
|
<div class="index-module__NV_5cW__chartCard">
|
||||||
|
<h3 class="chartCard__title">高校开源社团/俱乐部数量</h3>
|
||||||
|
<div class="chartCard__container">
|
||||||
|
<div class="num-empty" v-if="isEmpty"></div>
|
||||||
|
<div v-else id="communityChart" class="chart"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, nextTick, watch, onUnmounted, computed } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||||
|
|
||||||
|
// 不再接收外部数据,组件内部管理数据
|
||||||
|
const universityClubData = ref([
|
||||||
|
// 华东地区
|
||||||
|
{ university: '上海交通大学', club: 'SJTU-LUG (Linux User Group)' },
|
||||||
|
{ university: '上海交通大学', club: '开放原子开源俱乐部' },
|
||||||
|
{ university: '复旦大学', club: 'Fudan LUG' },
|
||||||
|
{ university: '复旦大学', club: '开放原子开源俱乐部' },
|
||||||
|
{ university: '浙江大学', club: 'ZJU-LUG' },
|
||||||
|
{ university: '浙江大学', club: 'AAA (Azure Availability Association)' },
|
||||||
|
{ university: '浙江大学', club: '开放原子开源俱乐部' },
|
||||||
|
{ university: '南京大学', club: 'NJU-LUG' },
|
||||||
|
{ university: '南京大学', club: 'eScience 协会' },
|
||||||
|
{ university: '南京大学', club: '开放原子开源俱乐部' },
|
||||||
|
{ university: '中国科学技术大学', club: 'USTC-LUG' },
|
||||||
|
{ university: '中国科学技术大学', club: 'VLAB' },
|
||||||
|
{ university: '同济大学', club: 'Tongji LUG' },
|
||||||
|
{ university: '同济大学', club: '开放原子开源俱乐部' },
|
||||||
|
{ university: '华东师范大学', club: 'ECNU LUG' },
|
||||||
|
// 华北地区
|
||||||
|
{ university: '清华大学', club: 'TUNA (清华大学学生网络与开源软件协会)' },
|
||||||
|
{ university: '清华大学', club: '开放原子开源俱乐部' },
|
||||||
|
{ university: '北京大学', club: 'PKU-LUG' },
|
||||||
|
{ university: '北京大学', club: '开源软件协会' },
|
||||||
|
{ university: '中国人民大学', club: 'RUC 开源社' },
|
||||||
|
{ university: '北京航空航天大学', club: 'BUAA-LUG' },
|
||||||
|
{ university: '北京理工大学', club: 'BIT-LUG' },
|
||||||
|
{ university: '南开大学', club: 'NKU-LUG' },
|
||||||
|
{ university: '天津大学', club: 'TJU-LUG' },
|
||||||
|
// 华南地区
|
||||||
|
{ university: '中山大学', club: 'SYSU-LUG' },
|
||||||
|
{ university: '华南理工大学', club: 'SCUT-LUG' },
|
||||||
|
{ university: '暨南大学', club: 'JNU-LUG' },
|
||||||
|
{ university: '深圳大学', club: 'SZU-LUG' },
|
||||||
|
{ university: '华南师范大学', club: 'SCNU-LUG' },
|
||||||
|
// 华中地区
|
||||||
|
{ university: '华中科技大学', club: 'HUST-LUG' },
|
||||||
|
{ university: '华中科技大学', club: '开放原子开源俱乐部' },
|
||||||
|
{ university: '武汉大学', club: 'WHU-LUG' },
|
||||||
|
{ university: '武汉大学', club: '珞珈码农社' },
|
||||||
|
{ university: '中南大学', club: 'CSU-LUG' },
|
||||||
|
{ university: '山东大学', club: 'SDU-LUG' },
|
||||||
|
{ university: '厦门大学', club: 'XMU-LUG' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 未来用于API调用的方法
|
||||||
|
const fetchDataFromApi = async () => {
|
||||||
|
try {
|
||||||
|
// 这里是预留的API调用位置,例如:
|
||||||
|
// const response = await fetch('/api/university-clubs-data');
|
||||||
|
// const apiData = await response.json();
|
||||||
|
// universityClubData.value = apiData;
|
||||||
|
|
||||||
|
// 暂时保留默认数据,实际使用时替换为API返回的数据
|
||||||
|
console.log('Fetching university club data from API...');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch university club data:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件挂载时获取数据
|
||||||
|
onMounted(() => {
|
||||||
|
fetchDataFromApi();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面尺寸响应式
|
||||||
|
const { widthType } = usePageResize();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 处理高校社团数据
|
||||||
|
const processUniversityClubsData = () => {
|
||||||
|
const counts: { [key: string]: { count: number; clubs: string[] } } = {};
|
||||||
|
|
||||||
|
universityClubData.value.forEach(item => {
|
||||||
|
if (!counts[item.university]) {
|
||||||
|
counts[item.university] = { count: 0, clubs: [] };
|
||||||
|
}
|
||||||
|
counts[item.university].count++;
|
||||||
|
counts[item.university].clubs.push(item.club);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 转换为数组并排序
|
||||||
|
const sorted = Object.entries(counts)
|
||||||
|
.map(([name, data]) => ({
|
||||||
|
name,
|
||||||
|
value: data.count,
|
||||||
|
clubs: data.clubs
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.value - a.value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
universities: sorted.slice(0, 10).map(item => item.name), // 取前10名,避免X轴过挤
|
||||||
|
values: sorted.slice(0, 10).map(item => item.value),
|
||||||
|
details: sorted.slice(0, 10).reduce((acc, item) => {
|
||||||
|
acc[item.name] = item.clubs;
|
||||||
|
return acc;
|
||||||
|
}, {} as { [key: string]: string[] })
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const communityData = computed(() => processUniversityClubsData());
|
||||||
|
|
||||||
|
// 空数据标识
|
||||||
|
const isEmpty = computed(() => {
|
||||||
|
return communityData.value.universities.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图表实例存储(用于resize时销毁重绘)
|
||||||
|
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
|
||||||
|
communityChart: null
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化高校开源社团/俱乐部数量图表(纵向柱状图)
|
||||||
|
const initCommunityChart = () => {
|
||||||
|
const el = document.getElementById('communityChart');
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
if (chartInstances.value.communityChart) {
|
||||||
|
chartInstances.value.communityChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
const myChart = echarts.init(el);
|
||||||
|
chartInstances.value.communityChart = myChart;
|
||||||
|
|
||||||
|
const data = communityData.value;
|
||||||
|
|
||||||
|
myChart.setOption({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'shadow' },
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
borderColor: '#3b82f6',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 6,
|
||||||
|
padding: 12,
|
||||||
|
textStyle: { color: '#333', fontSize: 13 },
|
||||||
|
formatter: (params: any) => {
|
||||||
|
const name = params[0].name;
|
||||||
|
const value = params[0].value;
|
||||||
|
const clubs = data.details[name] || [];
|
||||||
|
|
||||||
|
const clubsHtml = clubs.map(c => `• ${c}`).join('<br/>');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="line-height: 2;">
|
||||||
|
<div style="font-weight: bold; color: #3b82f6; margin-bottom: 6px; border-bottom: 2px solid #3b82f6; padding-bottom: 4px;">${name}</div>
|
||||||
|
<div>社团数量: <span style="color: #10b981; font-weight: 700; font-size: 16px;">${value}</span> 个</div>
|
||||||
|
<div style="margin-top: 8px; border-top: 1px solid rgba(0,0,0,0.1); padding-top: 6px; font-size: 11px; color: #666; max-height: 150px; overflow-y: auto;">
|
||||||
|
${clubsHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '10%',
|
||||||
|
right: '10%',
|
||||||
|
bottom: '5%',
|
||||||
|
top: '20%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: data.universities,
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 10,
|
||||||
|
rotate: 45,
|
||||||
|
interval: 0,
|
||||||
|
color: '#666',
|
||||||
|
margin: 15
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
alignWithLabel: true,
|
||||||
|
length: 5
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: { color: '#e5e7eb', width: 2 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: '社团数量',
|
||||||
|
nameTextStyle: { fontSize: 12, color: '#666' },
|
||||||
|
axisLabel: { fontSize: 12, color: '#666' },
|
||||||
|
minInterval: 1,
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: { color: 'rgba(0, 0, 0, 0.06)', type: 'dashed' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '社团数量',
|
||||||
|
type: 'bar',
|
||||||
|
data: data.values,
|
||||||
|
barWidth: '60%',
|
||||||
|
barCategoryGap: '30%',
|
||||||
|
itemStyle: {
|
||||||
|
color: (params: any) => {
|
||||||
|
// 根据排名使用不同颜色
|
||||||
|
const colors = [
|
||||||
|
['#3b82f6', '#60a5fa'], // 蓝色
|
||||||
|
['#8b5cf6', '#a78bfa'], // 紫色
|
||||||
|
['#ec4899', '#f472b6'], // 粉色
|
||||||
|
['#10b981', '#34d399'], // 绿色
|
||||||
|
['#f59e0b', '#fbbf24'] // 橙色
|
||||||
|
];
|
||||||
|
const colorPair = colors[params.dataIndex % colors.length];
|
||||||
|
return new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
|
{ offset: 0, color: colorPair[1] },
|
||||||
|
{ offset: 1, color: colorPair[0] }
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
borderRadius: [4, 4, 0, 0],
|
||||||
|
shadowBlur: 8,
|
||||||
|
shadowColor: 'rgba(59, 130, 246, 0.3)'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
formatter: '{c}',
|
||||||
|
fontSize: 11,
|
||||||
|
color: '#3b82f6',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 15,
|
||||||
|
shadowColor: 'rgba(59, 130, 246, 0.6)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
animationDuration: 1000,
|
||||||
|
animationEasing: 'cubicOut',
|
||||||
|
animationDelay: (idx: number) => idx * 50
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听数据变化,重新渲染图表
|
||||||
|
watch(universityClubData, () => {
|
||||||
|
if (!isEmpty.value) {
|
||||||
|
nextTick(() => {
|
||||||
|
initCommunityChart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
// 页面resize时重绘图表
|
||||||
|
watch(widthType, () => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (!isEmpty.value) {
|
||||||
|
initCommunityChart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 挂载时初始化
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isEmpty.value) {
|
||||||
|
initCommunityChart();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件卸载时销毁图表实例
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (chartInstances.value.communityChart) {
|
||||||
|
chartInstances.value.communityChart.dispose();
|
||||||
|
chartInstances.value.communityChart = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 高校开源社团/俱乐部数量图表卡片样式 - 从主页面复制 */
|
||||||
|
.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.25rem;
|
||||||
|
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: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__title {
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 容器样式 - 从主页面复制 */
|
||||||
|
.chartCard__container {
|
||||||
|
width: 100%;
|
||||||
|
height: 390px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空数据显示样式 - 从主页面复制 */
|
||||||
|
.num-empty {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表容器样式 */
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -134,13 +134,7 @@
|
|||||||
<InfrastructureCoverageChart />
|
<InfrastructureCoverageChart />
|
||||||
|
|
||||||
<!-- 3. 高校开源社团/俱乐部数量-->
|
<!-- 3. 高校开源社团/俱乐部数量-->
|
||||||
<div class="index-module__NV_5cW__chartCard">
|
<UniversityClubStatsChart />
|
||||||
<h3 class="chartCard__title">高校开源社团/俱乐部数量</h3>
|
|
||||||
<div class="chartCard__container">
|
|
||||||
<div class="num-empty" v-if="isEmpty.community"></div>
|
|
||||||
<div v-else id="communityChart" class="chart"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 4. 社区治理健康度对比-->
|
<!-- 4. 社区治理健康度对比-->
|
||||||
<div class="index-module__NV_5cW__chartCard">
|
<div class="index-module__NV_5cW__chartCard">
|
||||||
@@ -286,6 +280,7 @@ import { onMounted, ref, nextTick, watch, onUnmounted, computed } from 'vue';
|
|||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import TalentMapChart from './TalentMapChart.vue'
|
import TalentMapChart from './TalentMapChart.vue'
|
||||||
import InfrastructureCoverageChart from './components/InfrastructureCoverageChart.vue'
|
import InfrastructureCoverageChart from './components/InfrastructureCoverageChart.vue'
|
||||||
|
import UniversityClubStatsChart from './components/UniversityClubStatsChart.vue'
|
||||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||||
|
|
||||||
// 页面尺寸响应式
|
// 页面尺寸响应式
|
||||||
@@ -538,42 +533,12 @@ const chartData = ref({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理高校社团数据
|
|
||||||
const processUniversityClubsData = () => {
|
|
||||||
const counts: { [key: string]: { count: number; clubs: string[] } } = {};
|
|
||||||
|
|
||||||
chartData.value.universityClubs.forEach(item => {
|
|
||||||
if (!counts[item.university]) {
|
|
||||||
counts[item.university] = { count: 0, clubs: [] };
|
|
||||||
}
|
|
||||||
counts[item.university].count++;
|
|
||||||
counts[item.university].clubs.push(item.club);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 转换为数组并排序
|
|
||||||
const sorted = Object.entries(counts)
|
|
||||||
.map(([name, data]) => ({
|
|
||||||
name,
|
|
||||||
value: data.count,
|
|
||||||
clubs: data.clubs
|
|
||||||
}))
|
|
||||||
.sort((a, b) => b.value - a.value);
|
|
||||||
|
|
||||||
return {
|
|
||||||
universities: sorted.slice(0, 10).map(item => item.name), // 取前10名,避免X轴过挤
|
|
||||||
values: sorted.slice(0, 10).map(item => item.value),
|
|
||||||
details: sorted.slice(0, 10).reduce((acc, item) => {
|
|
||||||
acc[item.name] = item.clubs;
|
|
||||||
return acc;
|
|
||||||
}, {} as { [key: string]: string[] })
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const communityData = computed(() => processUniversityClubsData());
|
|
||||||
|
|
||||||
// 空数据标识
|
// 空数据标识
|
||||||
const isEmpty = ref({
|
const isEmpty = ref({
|
||||||
community: false,
|
|
||||||
communityHealth: false,
|
communityHealth: false,
|
||||||
languageTech: false
|
languageTech: false
|
||||||
});
|
});
|
||||||
@@ -642,7 +607,6 @@ const getTalentLevel = (density: number) => {
|
|||||||
|
|
||||||
// 图表实例存储(用于resize时销毁重绘)
|
// 图表实例存储(用于resize时销毁重绘)
|
||||||
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
|
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
|
||||||
communityChart: null,
|
|
||||||
communityHealthChart: null,
|
communityHealthChart: null,
|
||||||
languageTechChart: null,
|
languageTechChart: null,
|
||||||
vitalityChart: null,
|
vitalityChart: null,
|
||||||
@@ -796,131 +760,7 @@ const formatDevs = (num: number) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 初始化高校开源社团/俱乐部数量图表(纵向柱状图)
|
|
||||||
const initCommunityChart = () => {
|
|
||||||
const el = document.getElementById('communityChart');
|
|
||||||
if (!el) return;
|
|
||||||
|
|
||||||
if (chartInstances.value.communityChart) {
|
|
||||||
chartInstances.value.communityChart.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
const myChart = echarts.init(el);
|
|
||||||
chartInstances.value.communityChart = myChart;
|
|
||||||
|
|
||||||
const data = communityData.value;
|
|
||||||
|
|
||||||
myChart.setOption({
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: { type: 'shadow' },
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
||||||
borderColor: '#3b82f6',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 6,
|
|
||||||
padding: 12,
|
|
||||||
textStyle: { color: '#333', fontSize: 13 },
|
|
||||||
formatter: (params: any) => {
|
|
||||||
const name = params[0].name;
|
|
||||||
const value = params[0].value;
|
|
||||||
const clubs = data.details[name] || [];
|
|
||||||
|
|
||||||
const clubsHtml = clubs.map(c => `• ${c}`).join('<br/>');
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div style="line-height: 2;">
|
|
||||||
<div style="font-weight: bold; color: #3b82f6; margin-bottom: 6px; border-bottom: 2px solid #3b82f6; padding-bottom: 4px;">${name}</div>
|
|
||||||
<div>社团数量: <span style="color: #10b981; font-weight: 700; font-size: 16px;">${value}</span> 个</div>
|
|
||||||
<div style="margin-top: 8px; border-top: 1px solid rgba(0,0,0,0.1); padding-top: 6px; font-size: 11px; color: #666; max-height: 150px; overflow-y: auto;">
|
|
||||||
${clubsHtml}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '10%',
|
|
||||||
right: '10%',
|
|
||||||
bottom: '5%',
|
|
||||||
top: '20%',
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: data.universities,
|
|
||||||
axisLabel: {
|
|
||||||
fontSize: 10,
|
|
||||||
rotate: 45,
|
|
||||||
interval: 0,
|
|
||||||
color: '#666',
|
|
||||||
margin: 15
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
alignWithLabel: true,
|
|
||||||
length: 5
|
|
||||||
},
|
|
||||||
axisLine: {
|
|
||||||
lineStyle: { color: '#e5e7eb', width: 2 }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
name: '社团数量',
|
|
||||||
nameTextStyle: { fontSize: 12, color: '#666' },
|
|
||||||
axisLabel: { fontSize: 12, color: '#666' },
|
|
||||||
minInterval: 1,
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: { color: 'rgba(0, 0, 0, 0.06)', type: 'dashed' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '社团数量',
|
|
||||||
type: 'bar',
|
|
||||||
data: data.values,
|
|
||||||
barWidth: '60%',
|
|
||||||
barCategoryGap: '30%',
|
|
||||||
itemStyle: {
|
|
||||||
color: (params: any) => {
|
|
||||||
// 根据排名使用不同颜色
|
|
||||||
const colors = [
|
|
||||||
['#3b82f6', '#60a5fa'], // 蓝色
|
|
||||||
['#8b5cf6', '#a78bfa'], // 紫色
|
|
||||||
['#ec4899', '#f472b6'], // 粉色
|
|
||||||
['#10b981', '#34d399'], // 绿色
|
|
||||||
['#f59e0b', '#fbbf24'] // 橙色
|
|
||||||
];
|
|
||||||
const colorPair = colors[params.dataIndex % colors.length];
|
|
||||||
return new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
|
||||||
{ offset: 0, color: colorPair[1] },
|
|
||||||
{ offset: 1, color: colorPair[0] }
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
borderRadius: [4, 4, 0, 0],
|
|
||||||
shadowBlur: 8,
|
|
||||||
shadowColor: 'rgba(59, 130, 246, 0.3)'
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
position: 'top',
|
|
||||||
formatter: '{c}',
|
|
||||||
fontSize: 11,
|
|
||||||
color: '#3b82f6',
|
|
||||||
fontWeight: 'bold'
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
itemStyle: {
|
|
||||||
shadowBlur: 15,
|
|
||||||
shadowColor: 'rgba(59, 130, 246, 0.6)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
animationDuration: 1000,
|
|
||||||
animationEasing: 'cubicOut',
|
|
||||||
animationDelay: (idx: number) => idx * 50
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化社区治理健康度对比(纵向柱状图)
|
// 初始化社区治理健康度对比(纵向柱状图)
|
||||||
const initCommunityHealthChart = () => {
|
const initCommunityHealthChart = () => {
|
||||||
@@ -1928,13 +1768,11 @@ const initHeatmapChart = () => {
|
|||||||
const initCharts = () => {
|
const initCharts = () => {
|
||||||
// 空数据判断
|
// 空数据判断
|
||||||
isEmpty.value = {
|
isEmpty.value = {
|
||||||
community: chartData.value.universityClubs.length === 0,
|
|
||||||
communityHealth: chartData.value.communityHealth.github.every(item => item === 0),
|
communityHealth: chartData.value.communityHealth.github.every(item => item === 0),
|
||||||
languageTech: chartData.value.languageTech.mainstream.length === 0 && chartData.value.languageTech.emerging.length === 0
|
languageTech: chartData.value.languageTech.mainstream.length === 0 && chartData.value.languageTech.emerging.length === 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化各图表
|
// 初始化各图表
|
||||||
if (!isEmpty.value.community) nextTick(() => initCommunityChart());
|
|
||||||
if (!isEmpty.value.communityHealth) nextTick(() => initCommunityHealthChart());
|
if (!isEmpty.value.communityHealth) nextTick(() => initCommunityHealthChart());
|
||||||
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
|
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
|
||||||
nextTick(() => initVitalityChart());
|
nextTick(() => initVitalityChart());
|
||||||
|
|||||||
Reference in New Issue
Block a user