Files
Situation-Awareness-Platfor…/src/views/Jyh/Community/indexOld.vue

1409 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="index-module__NV_5cW__container">
<!-- 头部区域标题副标题武汉核心数据统计 -->
<div class="index-module__NV_5cW__header">
<div class="index-module__NV_5cW__floatingElements">
<div class="index-module__NV_5cW__floatingElement"></div>
<div class="index-module__NV_5cW__floatingElement"></div>
<div class="index-module__NV_5cW__floatingElement"></div>
</div>
<div class="index-module__NV_5cW__titleSection">
<div class="text-center max-w-4xl mx-auto">
<div class="flex items-center justify-center mb-5">
<div class="title-arrow-left"></div>
<h2 class="text-[32px] md:text-[40px] font-bold text-[#1a1a1a] px-5 tracking-[0.05em] leading-tight">
开源 · 武汉
</h2>
<div class="title-arrow-right"></div>
</div>
<p class="text-[#888888] text-base md:text-lg leading-relaxed max-w-3xl mx-auto">
汇聚武汉开源力量共建城市开源生态
</p>
</div>
<StatsBar />
</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">
<h3 class="chartCard__title">武汉优秀开源项目</h3>
<!-- 新增ref + 鼠标事件 -->
<div class="chartCard__container list-container"
ref="scrollContainer1"
@mouseenter="pauseScroll(1)"
@mouseleave="resumeScroll(1)">
<div class="project-list">
<div class="project-item" v-for="(item, index) in excellentProjects" :key="index">
<span class="project-rank">{{ index + 1 }}</span>
<div class="project-info">
<span class="project-name">{{ item.name }}</span>
<span class="project-desc">{{ item.desc }}</span>
</div>
<span class="project-org">{{ item.org }}</span>
</div>
</div>
</div>
</div>
<!-- 2. 高校开源参与分布饼图-->
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">高校开源参与分布</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.university"></div>
<div v-else id="universityChart" class="chart"></div>
</div>
</div>
<!-- 3. 开源社区区域分布柱状图-->
<div class="index-module__NV_5cW__chartCard">
<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. OSS Compass开源项目排行横向柱状图-->
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">OSS Compass 开源项目排行</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.ossCompass"></div>
<div v-else id="ossCompassChart" class="chart"></div>
</div>
</div>
<!-- 5. 武汉开源开发者排行榜横向柱状图-->
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">武汉开源开发者排行榜</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.developer"></div>
<div v-else id="developerChart" class="chart"></div>
</div>
</div>
<!-- 6. 2025武汉市优秀开源软件项目列表-->
<div class="index-module__NV_5cW__chartCard list-card">
<h3 class="chartCard__title">2025武汉市优秀开源软件项目</h3>
<!-- 新增ref + 鼠标事件 -->
<div class="chartCard__container list-container"
ref="scrollContainer2"
@mouseenter="pauseScroll(2)"
@mouseleave="resumeScroll(2)">
<div class="project-list">
<div class="project-item" v-for="(item, index) in excellentProjects2025" :key="index">
<span class="project-rank">{{ index + 1 }}</span>
<div class="project-info">
<span class="project-name">{{ item.name }}</span>
<span class="project-desc">{{ item.desc }}</span>
</div>
<span class="project-org">{{ item.org }}</span>
</div>
</div>
</div>
</div>
<!-- 7. 武汉市语言与技术竞争趋势折线图2020-2024-->
<div class="index-module__NV_5cW__chartCard chartCard__wide">
<h3 class="chartCard__title">武汉市编程语言占比趋势2020-2024</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.languageTech"></div>
<div v-else id="languageTechChart" class="chart"></div>
</div>
</div>
</div>
</div>
<!-- 加入武汉开源社区行动区 -->
<div class="index-module__NV_5cW__actionSection">
<div class="index-module__NV_5cW__actionBackground"></div>
<div class="index-module__NV_5cW__actionContent">
<div class="index-module__NV_5cW__actionIcon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-sparkles" aria-hidden="true">
<path
d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"></path>
<path d="M20 3v4"></path>
<path d="M22 5h-4"></path>
<path d="M4 17v2"></path>
<path d="M5 18H3"></path>
</svg>
</div>
<h3 class="index-module__NV_5cW__actionTitle">加入武汉开源社区</h3>
<p class="index-module__NV_5cW__actionDescription">
与武汉本地开发者企业高校携手共建开源生态共享开源成果参与社区活动与项目协作
</p>
<div class="index-module__NV_5cW__actionFeatures">
<div class="index-module__NV_5cW__feature">
<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"
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>
<div class="index-module__NV_5cW__feature">
<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"
class="lucide lucide-building2" aria-hidden="true">
<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"></path>
<path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"></path>
<path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"></path>
<path d="M10 6h4"></path>
<path d="M10 10h4"></path>
<path d="M10 14h4"></path>
<path d="M10 18h4"></path>
</svg>
<span>企业项目协作</span>
</div>
<div class="index-module__NV_5cW__feature">
<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"
class="lucide lucide-graduation-cap" aria-hidden="true">
<path d="M22 10v6M2 10l10-5 10 5-10 5z"></path>
<path d="M6 12v5c3 3 9 3 12 0v-5"></path>
</svg>
<span>高校产学研对接</span>
</div>
</div>
<button type="button"
class="ant-btn css-aa1tt5 ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-lg index-module__NV_5cW__createButton">
<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-user-plus" aria-hidden="true">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.12a4 4 0 0 1 0 7.76"></path>
</svg>
<span>加入武汉开源社区</span>
<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-arrow-right" aria-hidden="true">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, watch, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import { usePageResize } from '@/utils/hooks/usePageResize';
import StatsBar from './StatsBar.vue'
// 页面尺寸响应式
const { widthType } = usePageResize();
// 1. 武汉优秀开源项目数据
const excellentProjects = ref([
{ name: '调问DWSurvey', desc: '问卷表单', org: '极推科技(武汉)有限公司' },
{ name: 'Eova', desc: '低代码开发平台', org: '武汉简码科技有限公司' },
{ name: 'ClickVisual', desc: '轻量级日志查询、分析、报警可视化平台', org: '武汉初心科技有限公司' },
{ name: 'meta2d.js', desc: '2D图元组成的可视化引擎', org: '武汉乐吾乐科技有限责任公司' },
{ name: 'Juno', desc: '分布式微服务管理系统', org: '武汉斗鱼鱼乐网络科技有限公司' },
{ name: 'Opentalent 开源北极星', desc: '开源人才评价系统', org: '湖北大学' },
{ name: 'fountain', desc: '仓颉编程语言实现的JAVA Spring框架的国产替代', org: '江汉大学' },
{ name: 'deepin', desc: '深度操作系统', org: '武汉深之度科技有限公司' },
{ name: 'RustSBI', desc: '安全引导程序', org: '华中科技大学' },
{ name: 'NextDenovo', desc: '高效且精确的长读长纠错与组装工具', org: '武汉希望组生物科技有限公司' }
]);
// 2. 2025武汉市优秀开源软件项目
const excellentProjects2025 = ref([
{ name: '调问DWSurvey', desc: '问卷表单', org: '极推科技(武汉)有限公司' },
{ name: 'Eova', desc: '低代码开发平台', org: '武汉简码科技有限公司' },
{ name: 'ClickVisual', desc: '轻量级日志查询、分析、报警可视化平台', org: '武汉初心科技有限公司' },
{ name: 'meta2d.js', desc: '2D图元组成的可视化引擎', org: '武汉乐吾乐科技有限责任公司' },
{ name: 'Juno', desc: '分布式微服务管理系统', org: '武汉斗鱼鱼乐网络科技有限公司' },
{ name: 'Opentalent 开源北极星', desc: '开源人才评价系统', org: '湖北大学' },
{ name: 'fountain', desc: '仓颉编程语言实现的JAVA Spring框架的国产替代', org: '江汉大学' }
]);
// 3. 图表数据定义(已更新为新数据)
const chartData = ref({
// 高校开源参与分布(新的占比数据)
university: [
{ value: 28, name: '华中科技大学' },
{ value: 25, name: '武汉大学' },
{ value: 13, name: '江汉大学' },
{ value: 9, name: '湖北大学' },
{ value: 7, name: '武汉理工大学' },
{ value: 6, name: '华中师范大学' },
{ value: 6, name: '武汉科技大学' },
{ value: 5, name: '湖北工业大学' },
{ value: 1, name: '其他' }
],
// 开源社区区域分布(行政区+社区数量)
community: {
xAxis: ['洪山区', '武昌区', '东湖高新区', '江夏区', '东西湖区', '青山区'],
data: [22, 11, 9, 5, 2, 1]
},
// OSS Compass开源项目排行保留原有数据
ossCompass: {
yAxis: [
'openharmony/docs',
'mindspore/mindspore',
'openharmony/arkui_ace_engine',
'apache/doris',
'openharmony/interface_sdk-js',
'anolis-challenge/activity-school',
'PaddlePaddle/Paddle',
'StarRocks/starrocks',
'openharmony/xts_acts',
'ant-design/ant-design'
],
data: [91.55, 67.35, 62.07, 48.82, 44.06, 39.77, 39.55, 34.2, 30.39, 30.37],
// 完整分数数据用于tooltip
fullData: [
{ openRank: '91.55 | 30.86', quality: '92.06 | 8.905', wxb: '70.69 | -14.8', score: '84.77 | 8.32' },
{ openRank: '67.35 | 27.41', quality: '96.92 | 7.17', wxb: '98.915 | 7.1', score: '87.73 | 13.89' },
{ openRank: '62.07 | 17.98', quality: '71.69 | 2.885', wxb: '76.06 | 10.9', score: '69.94 | 10.59' },
{ openRank: '48.82 | 10.14', quality: '67.26 | 0.573', wxb: '30.5 | 7.3', score: '48.86 | 6.00' },
{ openRank: '44.06 | 11.49', quality: '32.5 | 3.14', wxb: '67.55 | 16.7', score: '48.04 | 10.44' },
{ openRank: '39.77 | 37.64', quality: '31.05 | 0.215', wxb: '24.798 | -18.9', score: '31.87 | 6.32' },
{ openRank: '39.55 | 7.25', quality: '68.55 | -2.937', wxb: '68.26 | -17.3', score: '58.79 | -4.33' },
{ openRank: '34.2 | 2.08', quality: '49.07 | -0.877', wxb: '47.07 | 9.9', score: '43.45 | 3.70' },
{ openRank: '30.39 | 10.97', quality: '29.8 | 5.633', wxb: '27.045 | 4.3', score: '29.08 | 6.97' },
{ openRank: '30.37 | 8.57', quality: '37.1 | 4.295', wxb: '35.097 | 6.8', score: '34.19 | 6.55' }
]
},
// 武汉开源开发者排行榜新的15位开发者总分数据
developer: {
yAxis: [
'白纯骢', '陆红旭', '蔡冬亮', '张继德', '蒋周奇',
'张天雄', '阮文俊', '李奇', '李成刚', '陈鹏轩',
'陈天宇', '彭友顺', '杨帆', '江彬', '韩凡宇'
],
data: [92, 88, 85, 83, 81, 79, 77, 75, 74, 73, 72, 70, 68, 65, 63]
},
// 武汉市编程语言占比趋势2020-2024
languageTech: {
xAxis: ['2020', '2021', '2022', '2023', '2024'],
series: [
{ name: 'JavaScript', data: [15.50, 17.00, 18.50, 19.80, 20.50] },
{ name: 'Python', data: [18.00, 18.50, 17.80, 16.50, 15.20] },
{ name: 'Java', data: [10.00, 11.50, 12.80, 13.50, 14.00] },
{ name: 'TypeScript', data: [16.50, 14.80, 12.50, 10.20, 8.50] },
{ name: 'C#', data: [6.50, 6.80, 7.20, 7.50, 7.80] },
{ name: 'PHP', data: [4.20, 5.00, 5.80, 6.50, 7.00] },
{ name: 'C++', data: [5.50, 5.80, 6.00, 6.30, 6.50] },
{ name: 'Go', data: [6.00, 5.60, 5.30, 4.80, 4.20] },
{ name: 'Rust', data: [5.00, 4.50, 3.80, 3.20, 2.80] },
{ name: 'Kotlin', data: [3.50, 3.20, 3.00, 2.80, 2.50] }
]
}
});
// 空数据标识
const isEmpty = ref({
university: false,
community: false,
ossCompass: false,
developer: false,
languageTech: false
});
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
universityChart: null,
communityChart: null,
ossCompassChart: null,
developerChart: null,
languageTechChart: 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;
}
});
});
// ===================== 新增结束 =====================
// 初始化高校参与分布饼图
const initUniversityChart = () => {
const el = document.getElementById('universityChart');
if (!el) return;
if (chartInstances.value.universityChart) {
chartInstances.value.universityChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.universityChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'item',
formatter: (params: any) => `${params.name}: ${params.value}%`
},
legend: {
bottom: '0',
textStyle: { fontSize: 10 }, // 缩小图例字体,适配更多高校
itemWidth: 10,
itemHeight: 10,
type: 'scroll' // 开启图例滚动,避免溢出
},
series: [
{
name: '参与度',
type: 'pie',
radius: ['40%', '70%'],
data: chartData.value.university,
label: {
show: true,
formatter: '{b}: {c}%',
fontSize: 9 // 缩小标签字体,适配更多高校
},
color: [
'#3b82f6', '#8b5cf6', '#ec4899', '#10b981', '#f59e0b',
'#6366f1', '#14b8a6', '#f43f5e', '#94a3b8' // 新增颜色匹配9个分类
]
}
],
grid: { left: 0, bottom: 0, top: 0, right: 0 }
});
};
// 初始化社区区域分布柱状图
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;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => `${params[0].name}: ${params[0].value}`
},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: chartData.value.community.xAxis,
axisLabel: { fontSize: 12 }
},
yAxis: {
type: 'value',
name: '社区数量',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 },
min: 0 // 强制y轴从0开始更直观
},
series: [
{
name: '开源社区数量',
type: 'bar',
data: chartData.value.community.data,
itemStyle: {
color: (params: any) => {
const colorList = ['#3b82f6', '#8b5cf6', '#ec4899', '#10b981', '#f59e0b', '#6366f1'];
return colorList[params.dataIndex];
}
}
}
]
});
};
// 初始化OSS Compass开源项目排行横向柱状图
const initOssCompassChart = () => {
const el = document.getElementById('ossCompassChart');
if (!el) return;
if (chartInstances.value.ossCompassChart) {
chartInstances.value.ossCompassChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.ossCompassChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => {
const index = params[0].dataIndex;
const data = chartData.value.ossCompass.fullData[index];
return `
<div>${params[0].name}</div>
<div>OpenRank: ${data.openRank}</div>
<div>代码内生质量: ${data.quality}</div>
<div>WXB分数: ${data.wxb}</div>
<div>综合分数: ${data.score}</div>
`;
}
},
grid: { left: '15%', right: '4%', bottom: '3%', top: '3%', containLabel: true },
xAxis: {
type: 'value',
name: 'OpenRank总分',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 }
},
yAxis: {
type: 'category',
data: chartData.value.ossCompass.yAxis,
axisLabel: { fontSize: 10, overflow: 'truncate', width: 120 }
},
series: [
{
name: 'OpenRank总分',
type: 'bar',
data: chartData.value.ossCompass.data,
itemStyle: { color: '#2951E0' },
label: {
show: true,
position: 'right',
formatter: '{c}',
fontSize: 10
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: 'rgba(41, 81, 224, 0.24)' },
{ offset: 1, color: 'rgba(41, 81, 224, 0.04)' }
])
}
}
]
});
};
// 初始化武汉开源开发者排行榜(横向柱状图)
const initDeveloperChart = () => {
const el = document.getElementById('developerChart');
if (!el) return;
if (chartInstances.value.developerChart) {
chartInstances.value.developerChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.developerChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => `${params[0].name}: ${params[0].value}贡献值`
},
grid: { left: '10%', right: '4%', bottom: '3%', top: '3%', containLabel: true },
xAxis: {
type: 'value',
name: '贡献值',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 },
min: 0 // 强制y轴从0开始
},
yAxis: {
type: 'category',
data: chartData.value.developer.yAxis,
axisLabel: { fontSize: 11 } // 缩小字体适配15个开发者
},
series: [
{
name: '贡献值',
type: 'bar',
data: chartData.value.developer.data,
itemStyle: { color: '#8b5cf6' },
label: {
show: true,
position: 'right',
formatter: '{c}',
fontSize: 11 // 缩小标签字体
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: 'rgba(139, 92, 246, 0.24)' },
{ offset: 1, color: 'rgba(139, 92, 246, 0.04)' }
])
}
}
]
});
};
// 初始化武汉市编程语言占比趋势(折线图)
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;
// 定义系列颜色10个颜色匹配10种语言
const colorList = [
'#3b82f6', '#10b981', '#f59e0b', '#ec4899', '#8b5cf6',
'#6366f1', '#14b8a6', '#f43f5e', '#94a3b8', '#475569'
];
const series = chartData.value.languageTech.series.map((item, index) => ({
name: item.name,
type: 'line',
data: item.data,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: colorList[index], width: 2 },
itemStyle: { color: colorList[index] },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: `${colorList[index]}33` },
{ offset: 1, color: `${colorList[index]}08` }
])
},
// 隐藏部分系列的图例,避免拥挤(可选,根据需求调整)
// legendHoverLink: index < 5 // 只显示前5个语言的图例交互
}));
myChart.setOption({
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
let res = `<div>${params[0].axisValue}</div>`;
params.forEach((p: any) => {
res += `<div>${p.seriesName}: ${p.value}%</div>`;
});
return res;
}
},
legend: {
top: 0,
textStyle: { fontSize: 10 },
type: 'scroll' // 开启图例滚动适配10种语言
},
grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true },
xAxis: {
type: 'category',
data: chartData.value.languageTech.xAxis,
axisLabel: { fontSize: 12 }
},
yAxis: {
type: 'value',
name: '占比(%',
nameTextStyle: { fontSize: 12 },
axisLabel: {
fontSize: 12,
formatter: '{value}%' // 显示百分比符号
},
min: 0,
max: 25 // 限制y轴最大值更直观
},
series
});
};
// 统一初始化图表
const initCharts = () => {
// 空数据判断
isEmpty.value = {
university: chartData.value.university.length === 0,
community: chartData.value.community.data.every(item => item === 0),
ossCompass: chartData.value.ossCompass.data.every(item => item === 0),
developer: chartData.value.developer.data.every(item => item === 0),
languageTech: chartData.value.languageTech.series.every(s => s.data.every(d => d === 0))
};
// 初始化各图表
if (!isEmpty.value.university) nextTick(() => initUniversityChart());
if (!isEmpty.value.community) nextTick(() => initCommunityChart());
if (!isEmpty.value.ossCompass) nextTick(() => initOssCompassChart());
if (!isEmpty.value.developer) nextTick(() => initDeveloperChart());
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
};
// 页面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;
}
.index-module__NV_5cW__header {
text-align: center;
margin-bottom: 3rem;
padding: 2rem 1rem 1rem;
position: relative;
}
.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);
}
}
// 加宽标题区域移除max-width限制调整宽度为100%,增加内边距
.index-module__NV_5cW__titleSection {
color: #333;
z-index: 2;
flex-direction: column;
align-items: center;
gap: 1rem;
display: flex;
position: relative;
width: 100%;
max-width: 1200px; // 增加最大宽度,适配大屏
margin: 0 auto; // 居中显示
padding: 0 2rem; // 增加左右内边距
}
.index-module__NV_5cW__titleContainer {
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 1rem;
display: flex;
width: 100%; // 宽度100%
}
.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: 800px; // 加宽副标题最大宽度
font-size: 1.1rem;
font-weight: 400;
display: flex;
width: 100%; // 宽度100%
justify-content: center; // 居中
}
// 加宽数据统计栏移除max-width限制调整为100%宽度,适配大屏
.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: 2rem; // 增加间距,更舒展
width: 100%; // 宽度100%
max-width: 100%; // 移除最大宽度限制
margin-top: 0.75rem;
padding: 1rem 2rem; // 增加内边距
display: flex;
justify-content: center; // 居中分布
flex-wrap: wrap; // 移动端自动换行
}
.index-module__NV_5cW__stat {
flex-direction: column;
align-items: center;
gap: 0.25rem;
display: flex;
flex: 1; // 平均分配宽度
min-width: 80px; // 最小宽度,防止挤压
}
.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;
}
/* 列表卡片特殊样式 */
.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;
}
.chartCard__wide {
grid-column: 1 / -1; /* 跨列显示 */
}
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 1rem 0;
text-align: center;
}
.chartCard__container {
width: 100%;
height: 300px; /* 图表高度 */
position: relative;
}
/* 列表容器样式:隐藏原生滚动条(可选,视觉更美观) */
.chartCard__container.list-container {
height: auto;
max-height: 300px; /* 限制列表高度,超出滚动 */
overflow-y: auto;
padding: 0 0.5rem;
/* 隐藏滚动条 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
/* 隐藏webkit内核浏览器的滚动条 */
.chartCard__container.list-container::-webkit-scrollbar {
display: none;
}
/* 项目列表样式 */
.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;
}
/* 与示例一致的空数据样式 */
.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%);
}
.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__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 {
gap: 1rem;
padding: 0.8rem 1rem;
flex-wrap: wrap;
}
.index-module__NV_5cW__stat {
flex: 1 1 60px;
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; /* 移动端图表高度调整 */
}
/* 移动端列表样式调整 */
.project-item {
flex-wrap: wrap;
gap: 0.5rem;
}
.project-org {
width: 100%;
max-width: 100%;
margin-top: 0.2rem;
}
// 移动端标题区域调整
.index-module__NV_5cW__titleSection {
padding: 0 1rem;
max-width: 100%;
}
}
@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; /* 小屏移动端图表高度调整 */
}
.index-module__NV_5cW__chartsGrid {
grid-template-columns: 1fr;
}
/* 小屏列表高度调整 */
.chartCard__container.list-container {
max-height: 300px;
}
}
/* 绘制标题左侧红色箭头(尖端指向文字) */
.title-arrow-left {
width: 0;
height: 0;
border-top: 9px solid transparent;
border-bottom: 9px solid transparent;
border-left: 12px solid #C8161D; /* 使用标准的实验室红色值 */
}
/* 绘制标题右侧红色箭头(尖端指向文字) */
.title-arrow-right {
width: 0;
height: 0;
border-top: 9px solid transparent;
border-bottom: 9px solid transparent;
border-right: 12px solid #C8161D;
}
/* 字体美化:确保标题厚重稳重 */
h2 {
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
}
/* 针对标签内文字的微调 */
span {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.more-btn {
display: inline-block;
background-color: #C8161D; /* 品牌红 */
color: #ffffff;
font-size: 14px;
font-weight: 600;
padding: 10px 38px;
border-radius: 6px;
letter-spacing: 0.5px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 12px rgba(200, 22, 29, 0.15);
&:hover {
background-color: #a31218;
transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(200, 22, 29, 0.25);
}
}
/* 文本截断 */
.line-clamp-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
/* 字体优化 */
h3, .more-btn {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Microsoft YaHei", sans-serif;
}
</style>