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

1175 lines
43 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__titleSection">
<div class="text-center max-w-4xl mx-auto header-content">
<div class="flex items-center justify-center mb-4 header-title-row">
<div class="title-decoration left"></div>
<h2 class="main-title">
开源 · 武汉
</h2>
<div class="title-decoration right"></div>
</div>
<p class="sub-title">
汇聚武汉开源力量共建城市开源生态
</p>
</div>
<StatsBar />
</div>
</div>
<div class="index-module__NV_5cW__content">
<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-activity" aria-hidden="true">
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
</svg>
<span>武汉开源生态数据可视化</span>
</h2>
</div>
<div class="index-module__NV_5cW__chartsGrid">
<div class="index-module__NV_5cW__chartCard list-card">
<h3 class="chartCard__title">武汉开源项目列表</h3>
<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 githubProjects" :key="index">
<div class="project-rank-wrapper">
<span class="project-rank">{{ index + 1 }}</span>
</div>
<div class="project-info">
<span class="project-name">{{ item.name }}</span>
<span class="project-desc">{{ item.desc }}</span>
</div>
<div class="project-meta">
<span class="project-stars"> {{ item.stars }}</span>
</div>
</div>
</div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<svg class="source-icon" viewBox="0 0 98 96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.128 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#333"/></svg>
<span class="source-name">GitHub</span>
</div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">中国城市开源项目数量对比</h3>
<div class="chartCard__container">
<div v-if="isEmpty.cityProject" class="num-empty"></div>
<div v-else id="cityProjectChart" class="chart"></div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<svg class="source-icon" viewBox="0 0 98 96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.128 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#333"/></svg>
<span class="source-name">GitHub</span>
</div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard list-card">
<div class="flex justify-center pb-2">
<div class="language-tab" @click="switchTab">
<span :class="{ active: activeTab === 0 }">开源创新联盟优秀项目</span>
<span :class="{ active: activeTab === 1 }">武汉优秀开源项目</span>
</div>
</div>
<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 currentProjectList" :key="index">
<div class="project-rank-wrapper">
<span class="project-rank" :class="{'rank-new': activeTab === 1}">{{ index + 1 }}</span>
</div>
<div class="project-info">
<span class="project-name">{{ item.name }}</span>
<span class="project-desc">{{ item.desc }}</span>
</div>
<span class="project-org-tag">{{ item.org }}</span>
</div>
</div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<div class="source-logo-text">Gov</div>
<span class="source-name">武汉市经信局/软件协会</span>
</div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">城市开源人才数量对比</h3>
<div class="chartCard__container">
<div v-if="isEmpty.talent" class="num-empty"></div>
<div v-else id="talentChart" class="chart"></div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<img src="@/assets/imgs/logo/gitcode-ico.png" class="source-logo-circle" />
<span class="source-name">GitCode</span>
</div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard list-card">
<h3 class="chartCard__title">武汉明星开发者贡献榜</h3>
<div class="chartCard__container list-container"
ref="developerScrollRef"
@mouseenter="pauseDeveloperScroll"
@mouseleave="resumeDeveloperScroll">
<div class="developer-list">
<div class="developer-item" v-for="(name, index) in developerNames" :key="index">
<!-- <div class="dev-rank" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>-->
<div class="dev-avatar" :style="{ background: getAvatarBg(index) }">
{{ name.charAt(0) }}
</div>
<div class="dev-info">
<div class="dev-name">{{ name }}</div>
<div class="dev-tag">核心贡献者</div>
</div>
<div class="dev-medal">
<svg v-if="index < 3" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="#f59e0b" stroke="none"><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"/></svg>
<span v-else class="dev-score">活跃</span>
</div>
</div>
</div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<img src="@/assets/imgs/logo/penguin_blue.svg" class="source-logo-circle" />
<span class="source-name">华科开放原子俱乐部</span>
</div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard">
<div class="flex justify-center pb-2">
<div class="language-tab" @click="switchCommunityTab">
<span :class="{ active: activeCommunityTab === 0 }">城市社区数量排行</span>
<span :class="{ active: activeCommunityTab === 1 }">武汉知名开源社区</span>
</div>
</div>
<div class="chartCard__container">
<div v-if="activeCommunityTab === 0" id="communityRankChart" class="chart"></div>
<div v-else class="list-container"
ref="communityScrollRef"
@mouseenter="pauseCommunityScroll"
@mouseleave="resumeCommunityScroll">
<div class="community-scroll-list">
<div class="community-item" v-for="(item, index) in wuhanCommunities" :key="index">
<div class="community-logo">
{{ item.name.charAt(0) }}
</div>
<div class="community-info">
<div class="community-top">
<span class="community-name">{{ item.name }}</span>
<span class="community-tag">{{ item.type }}</span>
</div>
<p class="community-slogan">{{ item.slogan }}</p>
</div>
<div class="community-status">
<span class="status-dot"></span>
活跃
</div>
</div>
</div>
</div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<img src="@/assets/imgs/logo/penguin_blue.svg" class="source-logo-circle" />
<span class="source-name">华科开放原子俱乐部</span>
</div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">重点城市高校开源社团数量</h3>
<div class="chartCard__container">
<div v-if="isEmpty.club" class="num-empty"></div>
<div v-else id="clubChart" class="chart"></div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<img src="@/assets/imgs/logo/penguin_blue.svg" class="source-logo-circle" />
<span class="source-name">华科开放原子俱乐部</span>
</div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">各省市开源相关政策发布数量</h3>
<div class="chartCard__container">
<div v-if="isEmpty.policy" class="num-empty"></div>
<div v-else id="policyChart" class="chart"></div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<img src="@/assets/imgs/logo/logo-kfyzjjh.png" class="source-logo-circle" />
<span class="source-name">开放原子开源基金会</span>
</div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">开放原子校园行·城市活动场次</h3>
<div class="chartCard__container">
<div v-if="isEmpty.campus" class="num-empty"></div>
<div v-else id="campusChart" class="chart"></div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<img src="@/assets/imgs/logo/logo-kfyzjjh.png" class="source-logo-circle" />
<span class="source-name">开放原子开源基金会</span>
</div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard chartCard__wide">
<h3 class="chartCard__title">全国重点城市开源镜像站/节点数量对比</h3>
<div class="chartCard__container">
<div v-if="isEmpty.mirrors" class="num-empty"></div>
<div v-else id="mirrorChart" class="chart"></div>
</div>
<div class="source-footer">
<span class="source-label">数据来源</span>
<div class="source-content">
<img src="@/assets/imgs/logo/penguin_blue.svg" class="source-logo-circle" />
<span class="source-name">华科开放原子俱乐部</span>
</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-rocket"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.1 4-1 4-1s.25 2.25 1 4c.45 1.04 1 2 2 3h-5z"/></svg>
</div>
<h3 class="index-module__NV_5cW__actionTitle">加入武汉开源社区</h3>
<p class="index-module__NV_5cW__actionDescription">与武汉本地开发者企业高校携手共建开源生态</p>
<button type="button" class="action-btn">
<span>加入我们</span>
</button>
</div>
</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';
import StatsBar from './StatsBar.vue'
const { widthType } = usePageResize();
// ================= 数据定义 =================
const githubProjects = ref([
{ name: 'DWSurvey', desc: '基于Vue+SpringBoot的问卷系统', stars: '3.2k' },
{ name: 'Eova', desc: '快速开发平台', stars: '2.8k' },
{ name: 'ClickVisual', desc: '日志分析平台', stars: '2.5k' },
{ name: 'Meta2d.js', desc: '2D可视化引擎', stars: '2.1k' },
{ name: 'Deepin Wine', desc: '在Linux运行Windows应用', stars: '6.5k' },
{ name: 'GoFrame', desc: 'Go语言企业级开发框架', stars: '11k' },
{ name: 'MindSpore', desc: '全场景AI计算框架(武汉所)', stars: '18k' },
{ name: 'IoTDB', desc: '物联网时序数据库', stars: '4.2k' }
]);
const activeTab = ref(0);
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": "仓颉语言版 Spring 框架替代", "org": "江汉大学" },
{ "name": "deepin", "desc": "深度桌面操作系统", "org": "深之度" },
{ "name": "RustSBI", "desc": "RISC-V 安全引导程序", "org": "华中科技大学" },
{ "name": "NextDenovo", "desc": "长读长序列组装工具", "org": "武汉希望组" }
]);
const chinaExcellentProjects = ref([
{
"name": "Deepin 深度操作系统",
"desc": "国内领先的国产 Linux 发行版,旨在为全球用户提供美观易用、安全可靠的操作系统体验,是国产软件生态的核心。",
"org": "武汉深之度"
},
{
"name": "Jupiter 微服务治理框架",
"desc": "由斗鱼研发并开源的面向治理的微服务框架,支持多数据源、多注册中心,适用于高并发场景。",
"org": "武汉斗鱼"
},
{
"name": "RustSBI 安全引导程序",
"desc": "基于 Rust 语言开发的 RISC-V 指令集体系结构安全引导程序,提供更高内存安全性。",
"org": "华中科技大学"
},
{
"name": "芯片开源 EDA 研发",
"desc": "致力于国产电子设计自动化工具开源化,解决芯片设计环节中的关键技术难题。",
"org": "华中科技大学"
},
{
"name": "LuoJiaSET 框架与 LuoJiaSET 样本库",
"desc": "专注于智能遥感影像解译,提供海量遥感样本数据集和深度学习框架。",
"org": "武汉大学"
},
{
"name": "鲲鹏安全库 (Kunpengsecl)",
"desc": "基于鲲鹏处理器的机密计算和受信任执行环境安全开发库,支撑云计算与隐私计算。",
"org": "武汉大学"
},
{
"name": "柚坛工具箱 (Uotan Toolbox)",
"desc": "针对 Android 发烧友开发的图形化综合工具箱,集成刷机、系统优化等多种功能。",
"org": "武昌理工学院"
},
{
"name": "凹语言 (Wa Language)",
"desc": "国内首个针对 WebAssembly 设计的通用编程语言,兼具 Web 灵活性与原生运行速度。",
"org": "武汉凹语言"
},
{
"name": "OpenHIS 开源医疗系统",
"desc": "开源的医院信息系统,旨在通过标准化、模块化方式赋能基层医疗机构数字化升级。",
"org": "武汉新致新源"
},
{
"name": "NextDenovo",
"desc": "高效且精确的长读长序列纠错和组装工具,主要用于基因组测序领域。",
"org": "武汉希望组生物科技有限公司"
}
]);
// 城市社区数量
const cityCommunityData = {
xAxis: ['北京', '上海', '深圳', '杭州', '武汉', '成都'],
data: [42, 38, 35, 28, 22, 18]
};
// 武汉知名社区数据
const wuhanCommunities = ref([
{ name: 'Deepin (深度)', type: '操作系统', slogan: '最大的国产桌面操作系统开源社区' },
{ name: 'OpenHarmony 武汉', type: '万物互联', slogan: '华科/武大等高校深度参与的终端操作系统' },
{ name: 'MindSpore 武汉', type: '人工智能', slogan: '昇思全场景AI框架武汉开发者社区' },
{ name: 'IoTDB 社区', type: '工业物联网', slogan: '源自清华在汉广泛应用的工业时序数据库' },
{ name: 'GoFrame 社区', type: '后端开发', slogan: '模块化、全栈式、模块化的Go语言开发框架' },
{ name: '飞桨 (PaddlePaddle)', type: '深度学习', slogan: '百度武汉开源生态开发者汇聚地' },
{ name: '极狐 GitLab 武汉', type: 'DevOps', slogan: '开源信息基础设施与协同开发平台' }
]);
const activeCommunityTab = ref(0);
const communityScrollRef = ref<HTMLElement | null>(null);
let communityTimer: NodeJS.Timeout | null = null;
const switchCommunityTab = () => {
if (activeCommunityTab.value === 1) {
activeCommunityTab.value = 0
nextTick(() => initCommunityRankChart());
pauseCommunityScroll();
} else {
activeCommunityTab.value = 1
nextTick(() => startCommunityScroll());
}
};
const startCommunityScroll = () => {
const container = communityScrollRef.value;
if (!container) return;
const list = container.querySelector('.community-scroll-list') as HTMLElement;
if (!list || list.scrollHeight <= container.clientHeight) return;
if (communityTimer) clearInterval(communityTimer);
communityTimer = setInterval(() => {
container.scrollTop += 1;
if (container.scrollTop >= (list.scrollHeight - container.clientHeight)) {
container.scrollTop = 0;
}
}, 45);
};
const pauseCommunityScroll = () => communityTimer && clearInterval(communityTimer);
const resumeCommunityScroll = () => activeCommunityTab.value === 1 && startCommunityScroll();
const currentProjectList = computed(() => {
return activeTab.value === 0 ? chinaExcellentProjects.value : excellentProjects.value;
});
const switchTab = (index: number) => {
if(activeTab.value===1) {
activeTab.value = 0
} else {
activeTab.value = 1
}
nextTick(() => {
if (scrollContainer2.value) scrollContainer2.value.scrollTop = 0;
pauseScroll(2);
resumeScroll(2);
});
};
const chartData = ref({
cityProject: {
xAxis: ['北京', '上海', '深圳', '杭州', '武汉', '成都', '广州'],
data: [12500, 11200, 9800, 8500, 6200, 5800, 5400]
},
talent: {
xAxis: ['北京', '深圳', '上海', '杭州', '武汉', '南京'],
data: [45000, 38000, 36000, 28000, 22000, 15000]
},
communityRank: {
xAxis: ['Deepin', 'OpenHarmony', 'MindSpore', 'IoTDB', 'GoFrame'],
data: [95, 92, 88, 76, 72]
},
club: {
xAxis: ['北京', '武汉', '上海', '西安', '南京'],
data: [28, 24, 20, 18, 15]
},
policy: {
xAxis: ['北京', '上海', '广东', '江苏', '湖北', '浙江'],
data: [12, 10, 9, 8, 7, 6]
},
campus: {
xAxis: ['武汉', '北京', '成都', '深圳', '西安'],
data: [35, 32, 25, 20, 18]
},
mirrors: {
xAxis: ['清华TUNA', '中科大', '阿里', '华为', '华科HUST', '网易', '腾讯'],
data: [520, 480, 450, 420, 380, 300, 280]
}
});
// 数据定义
const developerNames = ['白纯骢', '陆红旭', '蔡冬亮', '张继德', '蒋周奇', '张天雄', '阮文俊', '李奇', '陈鹏轩', '杨帆'];
const developerScrollRef = ref<HTMLElement | null>(null);
let developerTimer: NodeJS.Timeout | null = null;
// 自动生成头像背景色
const getAvatarBg = (index: number) => {
const colors = ['#6366f1', '#8b5cf6', '#ec4899', '#f43f5e', '#f59e0b', '#10b981', '#06b6d4', '#3b82f6'];
return colors[index % colors.length];
};
// 滚动逻辑
const startDeveloperScroll = () => {
const container = developerScrollRef.value;
if (!container) return;
const list = container.querySelector('.developer-list') as HTMLElement;
if (!list || list.scrollHeight <= container.clientHeight) return;
if (developerTimer) clearInterval(developerTimer);
developerTimer = setInterval(() => {
container.scrollTop += 1;
if (container.scrollTop >= (list.scrollHeight - container.clientHeight)) {
container.scrollTop = 0;
}
}, 40);
};
const pauseDeveloperScroll = () => developerTimer && clearInterval(developerTimer);
const resumeDeveloperScroll = () => startDeveloperScroll();
const isEmpty = ref({
cityProject: false, talent: false, developer: false, communityRank: false,
club: false, policy: false, campus: false, mirrors: false
});
const chartInstances = ref<{[key: string]: echarts.ECharts | null}>({});
const scrollContainer1 = ref<HTMLDivElement | null>(null);
const scrollContainer2 = ref<HTMLDivElement | null>(null);
const scrollTimers: { [key: number]: NodeJS.Timeout | null } = { 1: null, 2: null };
const autoScroll = (key: number) => {
const container = key === 1 ? scrollContainer1.value : scrollContainer2.value;
if (!container) return;
const list = container.querySelector('.project-list') as HTMLDivElement;
if (!list || list.scrollHeight <= container.clientHeight) {
if (scrollTimers[key]) clearInterval(scrollTimers[key]!);
return;
}
if (scrollTimers[key]) clearInterval(scrollTimers[key]!);
scrollTimers[key] = setInterval(() => {
container.scrollTop += 1;
if (list.scrollHeight - container.scrollTop <= container.clientHeight + 1) {
container.scrollTop = 0;
}
}, 40);
};
const pauseScroll = (key: number) => { if (scrollTimers[key]) clearInterval(scrollTimers[key]!); };
const resumeScroll = (key: number) => autoScroll(key);
// ================= 图表初始化=================
const commonGrid = { left: '3%', right: '4%', bottom: '15px', top: '15%', containLabel: true };
const colorPalette = ['#3b82f6', '#06b6d4', '#8b5cf6', '#ec4899', '#f59e0b']; // 蓝, 青, 紫, 粉, 橙
const initCityProjectChart = () => {
const el = document.getElementById('cityProjectChart');
if(!el) return;
const myChart = echarts.init(el);
chartInstances.value.cityProject = myChart;
myChart.setOption({
tooltip: { trigger: 'axis', valueFormatter: (value: any) => value + ' 个' },
grid: commonGrid,
xAxis: { type: 'category', data: chartData.value.cityProject.xAxis, axisLabel: { color: '#666', fontSize: 11 } },
yAxis: { type: 'value',name: '单位:个', splitLine: { lineStyle: { type: 'dashed', color: '#eee' } } },
series: [{
type: 'bar', data: chartData.value.cityProject.data, barWidth: '50%',
itemStyle: { borderRadius: [4, 4, 0, 0], color: new echarts.graphic.LinearGradient(0,0,0,1,[{offset:0,color:'#4F46E5'},{offset:1,color:'#818CF8'}]) }
}]
});
};
const initTalentChart = () => {
const el = document.getElementById('talentChart');
if(!el) return;
const myChart = echarts.init(el);
chartInstances.value.talent = myChart;
myChart.setOption({
tooltip: { trigger: 'axis', valueFormatter: (value: any) => value + ' 人' },
grid: commonGrid,
xAxis: { type: 'category', data: chartData.value.talent.xAxis, axisLabel: { color: '#666' } },
yAxis: { type: 'value',name: '单位:人', splitLine: { lineStyle: { type: 'dashed', color: '#eee' } } },
series: [{
type: 'bar', data: chartData.value.talent.data, barWidth: '50%',
itemStyle: { borderRadius: [4, 4, 0, 0], color: '#F97316' } // 橙色
}]
});
};
const initDeveloperChart = () => {
const el = document.getElementById('developerChart');
if(!el) return;
const myChart = echarts.init(el);
chartInstances.value.developer = myChart;
myChart.setOption({
tooltip: { trigger: 'axis', valueFormatter: (value: any) => value + ' 贡献分' },
grid: { left: '3%', right: '5%', bottom: '15px', top: '5%', containLabel: true },
xAxis: { type: 'value', show: false }, // 隐藏x轴
yAxis: { type: 'category', data: chartData.value.developer.yAxis.reverse(), axisLine: {show: false}, axisTick: {show: false}, axisLabel: { color: '#333', fontWeight: 500 } },
series: [{
type: 'bar', data: chartData.value.developer.data.reverse(), barWidth: 16,
itemStyle: { color: new echarts.graphic.LinearGradient(1,0,0,0,[{offset:0,color:'#7C3AED'},{offset:1,color:'#C4B5FD'}]), borderRadius: [0, 8, 8, 0] },
label: { show: false } // **修改点:隐藏具体分数**
}]
});
};
const initCommunityRankChart = () => {
const el = document.getElementById('communityRankChart');
if(!el) return;
const myChart = echarts.init(el);
myChart.setOption({
tooltip: { trigger: 'axis', valueFormatter: (val: any) => val + ' 个' },
grid: { left: '3%', right: '4%', bottom: '15px', top: '22%', containLabel: true },
xAxis: { type: 'category', data: ['北京', '上海', '深圳', '杭州', '武汉', '成都'], axisLabel: { color: '#666' } },
yAxis: { type: 'value', name: '单位:个', nameTextStyle: { color: '#999' } },
series: [{
type: 'bar',
data: [42, 38, 35, 28, 22, 18],
barWidth: '40%',
itemStyle: { borderRadius: [4, 4, 0, 0], color: '#3b82f6' }
}]
});
};
const initClubChart = () => {
const el = document.getElementById('clubChart');
if(!el) return;
const myChart = echarts.init(el);
chartInstances.value.club = myChart;
myChart.setOption({
tooltip: { trigger: 'axis', valueFormatter: (value: any) => value + ' 个' },
grid: commonGrid,
xAxis: { type: 'category', data: chartData.value.club.xAxis, axisLabel: { color: '#666' } },
yAxis: { type: 'value',name: '单位:个', splitLine: { lineStyle: { type: 'dashed', color: '#eee' } } },
series: [{
type: 'line', data: chartData.value.club.data,
smooth: true, symbolSize: 8,
lineStyle: { width: 3, color: '#F59E0B' },
itemStyle: { color: '#F59E0B', borderColor: '#fff', borderWidth: 2 },
areaStyle: { opacity: 0.15, color: '#F59E0B' }
}]
});
};
const initPolicyChart = () => {
const el = document.getElementById('policyChart');
if(!el) return;
const myChart = echarts.init(el);
chartInstances.value.policy = myChart;
myChart.setOption({
tooltip: { trigger: 'axis', valueFormatter: (value: any) => value + ' 条' },
grid: commonGrid,
xAxis: { type: 'category', data: chartData.value.policy.xAxis, axisLabel: { color: '#666' } },
yAxis: { type: 'value', name: '单位:条',splitLine: { lineStyle: { type: 'dashed', color: '#eee' } } },
series: [{
type: 'bar', data: chartData.value.policy.data, barWidth: '45%',
itemStyle: { borderRadius: [4, 4, 0, 0], color: '#2563EB' } // 蓝色
}]
});
};
const initCampusChart = () => {
const el = document.getElementById('campusChart');
if(!el) return;
const myChart = echarts.init(el);
chartInstances.value.campus = myChart;
// **修改点去掉三角形pictorialBar改为美观的圆角柱状图**
myChart.setOption({
tooltip: { trigger: 'axis', valueFormatter: (value: any) => value + ' 场' },
grid: commonGrid,
xAxis: { type: 'category', data: chartData.value.campus.xAxis, axisLabel: { color: '#666' } },
yAxis: { type: 'value',name: '单位:场次', splitLine: { lineStyle: { type: 'dashed', color: '#eee' } } },
series: [{
type: 'bar',
data: chartData.value.campus.data,
barWidth: '35%',
itemStyle: {
borderRadius: [8, 8, 0, 0], // 圆角
// 活力洋红色渐变
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#EC4899' },
{ offset: 1, color: '#F472B6' }
])
}
}]
});
};
const initMirrorChart = () => {
const el = document.getElementById('mirrorChart');
if(!el) return;
const myChart = echarts.init(el);
chartInstances.value.mirrors = myChart;
myChart.setOption({
tooltip: { trigger: 'axis', valueFormatter: (value: any) => value + ' 个节点' },
grid: { left: '3%', right: '3%', bottom: '15px', top: '15%', containLabel: true },
xAxis: { type: 'category', data: chartData.value.mirrors.xAxis, axisLabel: { color: '#666' } },
yAxis: { type: 'value',name: '单位:个', splitLine: { lineStyle: { type: 'dashed', color: '#eee' } } },
series: [{
type: 'bar', data: chartData.value.mirrors.data,
barWidth: 30,
itemStyle: {
borderRadius: [4, 4, 0, 0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#0EA5E9' },
{ offset: 1, color: '#38BDF8' }
])
}
}]
});
};
const initCharts = () => {
nextTick(() => {
initCityProjectChart(); initTalentChart(); initDeveloperChart();
initCommunityRankChart(); initClubChart(); initPolicyChart();
initCampusChart(); initMirrorChart();
});
};
const init = () => {
nextTick(() => {
initCityProjectChart(); initTalentChart(); initDeveloperChart();
initCommunityRankChart(); initClubChart(); initPolicyChart();
initCampusChart(); initMirrorChart();
autoScroll(1); autoScroll(2);startCommunityScroll();startDeveloperScroll();
});
}
const unInit = () => {
Object.values(scrollTimers).forEach(t => t && clearInterval(t));
pauseDeveloperScroll();
pauseCommunityScroll();
}
watch(widthType, () => { init() });
onMounted(() => { init() });
onUnmounted(() => { unInit() });
</script>
<style lang="scss" scoped>
.index-module__NV_5cW__container {
min-height: 100vh;
//background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%); /* 深蓝到深紫背景 */
padding: 2rem 1rem;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
color: #fff;
}
/* 头部样式 */
.index-module__NV_5cW__header {
width: 1400px;
text-align: center;
padding: 1rem;
position: relative;
margin: 0 auto 3rem;
}
.header-content {
position: relative;
z-index: 10;
margin-bottom: 24px;
}
.header-title-row {
gap: 1.5rem;
}
.main-title {
font-size: 3rem;
font-weight: 800;
color: #fff;
letter-spacing: 2px;
background: linear-gradient(to right, #fff, #93c5fd);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 30px rgba(59, 130, 246, 0.5);
}
.sub-title {
color: #94a3b8;
font-size: 1.1rem;
font-weight: 300;
letter-spacing: 1px;
}
.title-decoration {
width: 60px; height: 2px;
background: linear-gradient(90deg, transparent, #60a5fa);
}
.title-decoration.right {
background: linear-gradient(90deg, #60a5fa, transparent);
}
/* 标题栏图表 Icon */
.index-module__NV_5cW__sectionHeader {
display: flex; justify-content: flex-start; margin-bottom: 1.5rem;
max-width: 1400px; margin: 0 auto 1.5rem auto; padding: 0 0.5rem;
}
.index-module__NV_5cW__sectionTitle {
display: flex; align-items: center; gap: 0.8rem; font-size: 1.4rem; font-weight: 600; color: #fff;
}
/* 网格系统 */
.index-module__NV_5cW__chartsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
gap: 1.5rem;
max-width: 1400px;
margin: 0 auto;
}
/* 卡片玻璃拟态美化 */
.index-module__NV_5cW__chartCard {
background: rgba(255, 255, 255, 0.95); /* 高不透明度白底,保证图表清晰 */
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 1.25rem;
box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
transition: transform 0.3s ease, box-shadow 0.3s ease;
position: relative;
overflow: hidden;
}
.index-module__NV_5cW__chartCard:hover {
transform: translateY(-5px);
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.4);
}
.chartCard__wide { grid-column: 1 / -1; }
.chartCard__title {
font-size: 1.05rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 1rem;
text-align: center;
position: relative;
z-index: 1;
}
.chartCard__container {
width: 100%; height: 300px; position: relative;
}
/* 列表样式优化 */
.list-container {
height: 300px; overflow-y: hidden;
padding: 0 4px;
}
.project-list { display: flex; flex-direction: column; gap: 0.6rem; padding-bottom: 1rem; }
.project-item {
display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem;
background: #f8fafc; border-radius: 8px; border: 1px solid #e2e8f0;
transition: all 0.2s;
}
.project-item:hover {
background: #fff; border-color: #3b82f6; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
}
.project-rank-wrapper {
width: 24px; display: flex; justify-content: center;
}
.project-rank {
width: 20px; height: 20px; border-radius: 6px;
background: #cbd5e1; color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 700;
}
/* 前三名高亮 */
.project-item:nth-child(1) .project-rank { background: linear-gradient(135deg, #fbbf24, #d97706); }
.project-item:nth-child(2) .project-rank { background: linear-gradient(135deg, #94a3b8, #64748b); }
.project-item:nth-child(3) .project-rank { background: linear-gradient(135deg, #dba789, #b07d62); }
.project-rank.rank-new { background: linear-gradient(135deg, #f43f5e, #e11d48) !important; }
.project-info { flex: 1; display: flex; flex-direction: column; gap: 2px; overflow: hidden; }
.project-name { font-size: 13px; font-weight: 700; color: #333; }
.project-desc { font-size: 11px; color: #64748b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.project-stars { font-size: 11px; font-weight: 600; color: #f59e0b; background: #fffbeb; padding: 2px 6px; border-radius: 4px; }
.project-org-tag { font-size: 11px; color: #3b82f6; background: #eff6ff; padding: 2px 6px; border-radius: 4px; white-space: nowrap; }
/* Tabs 美化 */
.tabs-header {
display: flex; justify-content: center; gap: 4px;
background: #f1f5f9; padding: 4px; border-radius: 8px;
margin: 0 auto 0.8rem auto; width: fit-content;
}
.tab-pill {
font-size: 12px; padding: 4px 12px; cursor: pointer; border-radius: 6px;
color: #64748b; font-weight: 500; transition: all 0.2s;
}
.tab-pill.active {
background: #fff; color: #3b82f6; font-weight: 600;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/* 来源 Footer 样式 (修改版数据来源Icon + Text) */
.source-footer {
margin-top: auto;
padding-top: 0.75rem;
border-top: 1px solid #f1f5f9;
display: flex;
align-items: center;
justify-content: flex-end; /* 右对齐 */
font-size: 11px;
}
.source-label {
color: #94a3b8; /* 浅灰色标签 */
margin-right: 4px;
}
.source-content {
display: flex;
align-items: center;
gap: 6px;
}
.source-icon { width: 14px; height: 14px; opacity: 0.8; }
.source-logo-circle { width: 18px; height: 18px; border-radius: 50%; box-shadow: 0 1px 2px rgba(0,0,0,0.1); }
.source-logo-text { font-size: 9px; font-weight: bold; background: #333; color: #fff; padding: 0 3px; border-radius: 2px; }
.source-name { color: #475569; font-weight: 500; } /* 深一点的灰色文字 */
/* 行动区域 */
.index-module__NV_5cW__actionSection {
max-width: 1400px; margin: 2rem auto 0;
text-align: center; border-radius: 24px; padding: 3rem;
background: rgba(30, 41, 59, 0.6); /* 深色背景 */
border: 1px solid rgba(255,255,255,0.1);
backdrop-filter: blur(12px);
position: relative; overflow: hidden;
}
.index-module__NV_5cW__actionBackground {
position: absolute; inset: 0;
background: radial-gradient(circle at top right, rgba(59, 130, 246, 0.15), transparent 40%);
z-index: 1;
}
.index-module__NV_5cW__actionContent { position: relative; z-index: 2; }
.index-module__NV_5cW__actionIcon {
color: #60a5fa; background: rgba(59, 130, 246, 0.1);
border-radius: 50%; width: 3.5rem; height: 3.5rem; margin: 0 auto 1.5rem;
display: flex; justify-content: center; align-items: center;
}
.index-module__NV_5cW__actionTitle { font-size: 1.8rem; font-weight: 700; color: #fff; margin-bottom: 0.5rem; }
.index-module__NV_5cW__actionDescription { color: #cbd5e1; margin-bottom: 1.5rem; }
.action-btn {
background: linear-gradient(135deg, #3b82f6, #6366f1);
border: none; color: white; padding: 0.8rem 2.5rem; border-radius: 50px;
font-weight: 600; cursor: pointer; font-size: 1rem;
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.4);
transition: all 0.3s;
}
.action-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(59, 130, 246, 0.5); }
/* 工具类 */
.chart { width: 100%; height: 100%; }
.num-empty { height: 2px; width: 50%; background: #ddd; margin: 50% auto; }
.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;
}
.community-scroll-list {
display: flex;
flex-direction: column;
gap: 10px;
padding-bottom: 20px;
}
.community-item {
display: flex;
align-items: center;
padding: 12px;
background: #f8fafc;
border-radius: 10px;
border: 1px solid #eef2f6;
transition: all 0.3s ease;
}
.community-item:hover {
background: #ffffff;
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.08);
}
.community-logo {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #3b82f6, #6366f1);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 18px;
margin-right: 12px;
flex-shrink: 0;
}
.community-info {
flex: 1;
overflow: hidden;
}
.community-top {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.community-name {
font-weight: 700;
font-size: 14px;
color: #1e293b;
}
.community-tag {
font-size: 10px;
padding: 1px 6px;
background: #e0f2fe;
color: #0369a1;
border-radius: 4px;
}
.community-slogan {
font-size: 12px;
color: #64748b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.community-status {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: #10b981;
background: #ecfdf5;
padding: 2px 8px;
border-radius: 20px;
font-weight: 500;
}
.developer-list {
display: flex;
flex-direction: column;
gap: 8px;
padding: 4px 2px 20px 2px;
}
.developer-item {
display: flex;
align-items: center;
padding: 10px 12px;
background: #ffffff;
border: 1px solid #f1f5f9;
border-radius: 12px;
transition: all 0.2s ease;
}
.developer-item:hover {
transform: scale(1.02);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
border-color: #e2e8f0;
}
/* 排名样式 */
.dev-rank {
width: 24px;
font-weight: 800;
font-style: italic;
color: #94a3b8;
font-size: 14px;
}
.dev-rank.rank-1 { color: #fbbf24; font-size: 18px; }
.dev-rank.rank-2 { color: #94a3b8; font-size: 16px; }
.dev-rank.rank-3 { color: #b45309; font-size: 15px; }
/* 头像占位 */
.dev-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 16px;
margin-right: 12px;
box-shadow: inset 0 -2px 4px rgba(0,0,0,0.1);
}
.dev-info {
flex: 1;
}
.dev-name {
font-size: 14px;
font-weight: 700;
color: #1e293b;
}
.dev-tag {
font-size: 11px;
color: #64748b;
}
.dev-score {
font-size: 11px;
color: #10b981;
background: #f0fdf4;
padding: 2px 8px;
border-radius: 10px;
}
@media (max-width: 768px) {
.main-title { font-size: 2rem; }
.index-module__NV_5cW__chartsGrid { grid-template-columns: 1fr; }
}
</style>