态势感知平台-开源生态与贡献价值全景-明星开发者聚焦组件抽离
This commit is contained in:
363
src/views/Jyh/ecosystem/components/StarDevelopersChart.vue
Normal file
363
src/views/Jyh/ecosystem/components/StarDevelopersChart.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<div class="index-module__NV_5cW__chartCard list-card">
|
||||
<h3 class="chartCard__title">明星开发者聚焦</h3>
|
||||
<!-- 新增:ref + 鼠标事件 -->
|
||||
<div class="chartCard__container list-container developer-list-container" ref="scrollContainer2"
|
||||
@mouseenter="pauseScroll(2)" @mouseleave="resumeScroll(2)">
|
||||
<div class="project-list developer-list">
|
||||
<div class="project-item developer-item" v-for="(item, index) in excellentProjects2025" :key="index">
|
||||
<div class="dev-avatar">
|
||||
<img :src="item.avatar" :alt="item.name" class="avatar-img" @error="handleImageError" />
|
||||
</div>
|
||||
<span class="project-rank dev-rank">{{ item.desc }}</span>
|
||||
<div class="project-info dev-info">
|
||||
<span class="project-name dev-name">{{ item.name }}</span>
|
||||
<span class="project-desc dev-location">📍 {{ item.org }}</span>
|
||||
</div>
|
||||
<span class="project-org dev-followers">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2">
|
||||
<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>
|
||||
</svg>
|
||||
{{ formatFollowers(item.followers) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
// 明星开发者数据(基于GitHub中国区Top开发者)
|
||||
const excellentProjects2025 = ref([
|
||||
{ name: '稚晖', desc: 'TOP 1', org: 'Shanghai', followers: 82738, avatar: 'https://github.com/peng-zhihui.png' },
|
||||
{ name: 'Crypto Michael', desc: 'TOP 2', org: 'Beijing, China', followers: 37622, avatar: 'https://github.com/michaelliao.png' },
|
||||
{ name: '代码家', desc: 'TOP 3', org: 'Beijing, China', followers: 24743, avatar: 'https://github.com/daimajia.png' },
|
||||
{ name: 'Jackson Tian', desc: 'TOP 4', org: 'Hangzhou, China', followers: 21267, avatar: 'https://github.com/JacksonTian.png' },
|
||||
{ name: '云风', desc: 'TOP 5', org: 'China', followers: 21192, avatar: 'https://github.com/cloudwu.png' },
|
||||
{ name: 'Fengda Huang', desc: 'TOP 6', org: 'Shanghai / Hangzhou', followers: 20229, avatar: 'https://github.com/phodal.png' },
|
||||
{ name: '程序员鱼皮', desc: 'TOP 7', org: 'Shanghai', followers: 18461, avatar: 'https://github.com/liyupi.png' },
|
||||
{ name: 'Trinea', desc: 'TOP 8', org: 'HangZhou', followers: 17019, avatar: 'https://github.com/Trinea.png' },
|
||||
{ name: 'lifesinger', desc: 'TOP 9', org: 'Hangzhou, China', followers: 15894, avatar: 'https://github.com/lifesinger.png' },
|
||||
{ name: 'stormzhang', desc: 'TOP 10', org: 'Shanghai, China', followers: 15889, avatar: 'https://github.com/stormzhang.png' },
|
||||
{ name: 'Wenli Zhang', desc: 'TOP 11', org: 'Shanghai, China', followers: 15680, avatar: 'https://github.com/Ovilia.png' },
|
||||
{ name: 'Wei Wang', desc: 'TOP 12', org: 'Yokohama / China', followers: 15532, avatar: 'https://github.com/onevcat.png' },
|
||||
{ name: 'astaxie', desc: 'TOP 13', org: 'Shanghai, China', followers: 15022, avatar: 'https://github.com/astaxie.png' },
|
||||
{ name: '司徒正美', desc: 'TOP 14', org: 'China', followers: 14742, avatar: 'https://github.com/RubyLouvre.png' },
|
||||
{ name: '云谦', desc: 'TOP 15', org: 'HangZhou, China', followers: 14466, avatar: 'https://github.com/sorrycc.png' },
|
||||
{ name: 'cangdu', desc: 'TOP 16', org: 'Shanghai, China', followers: 13728, avatar: 'https://github.com/bailicangdu.png' },
|
||||
{ name: 'Huang Haiguang', desc: 'TOP 17', org: 'Qingdao, China', followers: 13422, avatar: 'https://github.com/fengdu78.png' },
|
||||
{ name: '花裤衩', desc: 'TOP 18', org: 'Shanghai, China', followers: 13236, avatar: 'https://github.com/PanJiaChen.png' },
|
||||
]);
|
||||
|
||||
// 滚动相关变量
|
||||
const scrollContainer2 = ref<HTMLDivElement | null>(null);
|
||||
const scrollTimers: { [key: number]: NodeJS.Timeout | null } = { 2: null };
|
||||
|
||||
// 3. 滚动配置
|
||||
const scrollStep = 1; // 每次滚动的步长(像素,越小越流畅)
|
||||
const scrollInterval = 20; // 滚动间隔(毫秒,越小越流畅)
|
||||
|
||||
// 4. 通用自动滚动函数
|
||||
const autoScroll = (containerKey: number) => {
|
||||
// 获取对应容器
|
||||
const container = containerKey === 2 ? scrollContainer2.value : null;
|
||||
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(() => {
|
||||
if (scrollTimers[2]) {
|
||||
clearInterval(scrollTimers[2]!);
|
||||
scrollTimers[2] = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 8. 格式化粉丝数(82738 → 82.7K)
|
||||
const formatFollowers = (num: number) => {
|
||||
if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
};
|
||||
|
||||
// 9. 图片加载失败处理
|
||||
const handleImageError = (e: Event) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
const name = target.alt;
|
||||
target.style.display = 'none';
|
||||
if (target.parentElement) {
|
||||
target.parentElement.innerHTML = `<span class="avatar-text">${name.slice(0, 1)}</span>`;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 启动自动滚动
|
||||
autoScroll(2);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.index-module__NV_5cW__chartCard {
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(10px);
|
||||
background: #ffffffe6; /* rgba(255, 255, 255, 0.9) */
|
||||
border: 1px solid #ffffff4d; /* rgba(255, 255, 255, 0.3) */
|
||||
border-radius: 1rem; /* 16px */
|
||||
padding: 1.5rem; /* 24px */
|
||||
box-shadow: 0 4px 16px #00000014; /* subtle shadow */
|
||||
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;
|
||||
}
|
||||
|
||||
.index-module__NV_5cW__chartCard.list-card {
|
||||
padding: 1.5rem; /* 24px */
|
||||
}
|
||||
|
||||
.chartCard__title {
|
||||
color: #333;
|
||||
font-size: 1.1rem; /* 17.6px */
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chartCard__container.list-container {
|
||||
width: 100%;
|
||||
height: 390px; /* 保持一致的高度 */
|
||||
position: relative;
|
||||
overflow: hidden; /* 外层容器隐藏溢出 */
|
||||
}
|
||||
|
||||
.list-container {
|
||||
height: auto;
|
||||
max-height: 300px; /* 限制列表高度,超出滚动 */
|
||||
overflow-y: auto; /* 允许垂直滚动 */
|
||||
padding: 0 0.5rem; /* 8px */
|
||||
/* 隐藏滚动条 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
.list-container::-webkit-scrollbar {
|
||||
display: none; /* Chrome/Safari/Opera */
|
||||
}
|
||||
|
||||
/* 隐藏webkit内核浏览器的滚动条 */
|
||||
.chartCard__container.list-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.developer-list-container {
|
||||
max-height: 400px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.project-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.project-item.developer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem; /* 12px */
|
||||
padding: 0.75rem 1rem; /* 12px 16px */
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||
border-left: 3px solid #3b82f6;
|
||||
border-radius: 0.5rem; /* 8px */
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.project-item.developer-item:hover {
|
||||
background: linear-gradient(135deg, #eff6ff 0%, #f0f4ff 100%);
|
||||
border-left-color: #8b5cf6;
|
||||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.15);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.dev-avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.project-rank.dev-rank {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #ec4899 0%, #f59e0b 100%);
|
||||
padding: 3px 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 2px 6px rgba(236, 72, 153, 0.3);
|
||||
flex-shrink: 0;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.project-info.dev-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem; /* 5px */
|
||||
}
|
||||
|
||||
.project-name.dev-name {
|
||||
font-size: 15px; /* 15px */
|
||||
font-weight: 700; /* 更粗的字体 */
|
||||
color: #1e293b; /* 深灰色 */
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.project-desc.dev-location {
|
||||
font-size: 11px; /* 11px */
|
||||
color: #64748b; /* 灰蓝色 */
|
||||
font-style: normal; /* 取消斜体 */
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.project-org.dev-followers {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
color: #3b82f6;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
max-width: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.project-org.dev-followers svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.scroll-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem; /* 4px */
|
||||
padding: 0.25rem 0.5rem; /* 4px 8px */
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-radius: 0 0 0 0.5rem; /* 与原文件一致 */
|
||||
font-size: 0.7rem; /* 与原文件一致 */
|
||||
color: #3b82f6;
|
||||
z-index: 10;
|
||||
animation: bounce 2s ease-in-out infinite; /* 添加动画 */
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-2px); }
|
||||
}
|
||||
</style>
|
||||
@@ -136,33 +136,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 6. 明星开发者聚焦(列表)-->
|
||||
<div class="index-module__NV_5cW__chartCard list-card">
|
||||
<h3 class="chartCard__title">明星开发者聚焦</h3>
|
||||
<!-- 新增:ref + 鼠标事件 -->
|
||||
<div class="chartCard__container list-container developer-list-container" ref="scrollContainer2"
|
||||
@mouseenter="pauseScroll(2)" @mouseleave="resumeScroll(2)">
|
||||
<div class="project-list developer-list">
|
||||
<div class="project-item developer-item" v-for="(item, index) in excellentProjects2025" :key="index">
|
||||
<div class="dev-avatar">
|
||||
<img :src="item.avatar" :alt="item.name" class="avatar-img" @error="handleImageError" />
|
||||
</div>
|
||||
<span class="project-rank dev-rank">{{ item.desc }}</span>
|
||||
<div class="project-info dev-info">
|
||||
<span class="project-name dev-name">{{ item.name }}</span>
|
||||
<span class="project-desc dev-location">📍 {{ item.org }}</span>
|
||||
</div>
|
||||
<span class="project-org dev-followers">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2">
|
||||
<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>
|
||||
</svg>
|
||||
{{ formatFollowers(item.followers) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<StarDevelopersChart />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -248,6 +222,7 @@ import UniversityClubStatsChart from './components/UniversityClubStatsChart.vue'
|
||||
import CommunityGovernanceHealthChart from './components/CommunityGovernanceHealthChart.vue'
|
||||
import VitalityWaveChart from './components/VitalityWaveChart.vue'
|
||||
import StatsGlass from './components/StatsGlass.vue'
|
||||
import StarDevelopersChart from './components/StarDevelopersChart.vue'
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
|
||||
// 页面尺寸响应式
|
||||
@@ -365,29 +340,7 @@ const excellentProjects = computed(() => {
|
||||
}));
|
||||
});
|
||||
|
||||
// 2. 明星开发者数据(基于GitHub中国区Top开发者)
|
||||
const excellentProjects2025 = ref([
|
||||
{ name: '稚晖', desc: 'TOP 1', org: 'Shanghai', followers: 82738, avatar: 'https://github.com/peng-zhihui.png' },
|
||||
{ name: 'Crypto Michael', desc: 'TOP 2', org: 'Beijing, China', followers: 37622, avatar: 'https://github.com/michaelliao.png' },
|
||||
{ name: '代码家', desc: 'TOP 3', org: 'Beijing, China', followers: 24743, avatar: 'https://github.com/daimajia.png' },
|
||||
{ name: 'Jackson Tian', desc: 'TOP 4', org: 'Hangzhou, China', followers: 21267, avatar: 'https://github.com/JacksonTian.png' },
|
||||
{ name: '云风', desc: 'TOP 5', org: 'China', followers: 21192, avatar: 'https://github.com/cloudwu.png' },
|
||||
{ name: 'Fengda Huang', desc: 'TOP 6', org: 'Shanghai / Hangzhou', followers: 20229, avatar: 'https://github.com/phodal.png' },
|
||||
{ name: '程序员鱼皮', desc: 'TOP 7', org: 'Shanghai', followers: 18461, avatar: 'https://github.com/liyupi.png' },
|
||||
{ name: 'Trinea', desc: 'TOP 8', org: 'HangZhou', followers: 17019, avatar: 'https://github.com/Trinea.png' },
|
||||
{ name: 'lifesinger', desc: 'TOP 9', org: 'Hangzhou, China', followers: 15894, avatar: 'https://github.com/lifesinger.png' },
|
||||
{ name: 'stormzhang', desc: 'TOP 10', org: 'Shanghai, China', followers: 15889, avatar: 'https://github.com/stormzhang.png' },
|
||||
{ name: 'Wenli Zhang', desc: 'TOP 11', org: 'Shanghai, China', followers: 15680, avatar: 'https://github.com/Ovilia.png' },
|
||||
{ name: 'Wei Wang', desc: 'TOP 12', org: 'Yokohama / China', followers: 15532, avatar: 'https://github.com/onevcat.png' },
|
||||
{ name: 'astaxie', desc: 'TOP 13', org: 'Shanghai, China', followers: 15022, avatar: 'https://github.com/astaxie.png' },
|
||||
{ name: '司徒正美', desc: 'TOP 14', org: 'China', followers: 14742, avatar: 'https://github.com/RubyLouvre.png' },
|
||||
{ name: '云谦', desc: 'TOP 15', org: 'HangZhou, China', followers: 14466, avatar: 'https://github.com/sorrycc.png' },
|
||||
{ name: 'cangdu', desc: 'TOP 16', org: 'Shanghai, China', followers: 13728, avatar: 'https://github.com/bailicangdu.png' },
|
||||
{ name: 'Huang Haiguang', desc: 'TOP 17', org: 'Qingdao, China', followers: 13422, avatar: 'https://github.com/fengdu78.png' },
|
||||
{ name: '花裤衩', desc: 'TOP 18', org: 'Shanghai, China', followers: 13236, avatar: 'https://github.com/PanJiaChen.png' },
|
||||
{ name: '破娃酱', desc: 'TOP 19', org: 'China', followers: 13203, avatar: 'https://github.com/breakwa11.png' },
|
||||
{ name: 'Draven', desc: 'TOP 20', org: 'Beijing, China', followers: 13094, avatar: 'https://github.com/draveness.png' }
|
||||
]);
|
||||
|
||||
|
||||
// 3. 图表数据定义
|
||||
const chartData = ref({
|
||||
@@ -586,24 +539,6 @@ onUnmounted(() => {
|
||||
});
|
||||
});
|
||||
|
||||
// 8. 格式化粉丝数(82738 → 82.7K)
|
||||
const formatFollowers = (num: number) => {
|
||||
if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
};
|
||||
|
||||
// 9. 图片加载失败处理
|
||||
const handleImageError = (e: Event) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
const name = target.alt;
|
||||
target.style.display = 'none';
|
||||
if (target.parentElement) {
|
||||
target.parentElement.innerHTML = `<span class="avatar-text">${name.slice(0, 1)}</span>`;
|
||||
}
|
||||
};
|
||||
|
||||
// 10. 企业排名相关方法
|
||||
const getRankClass = (rank: number) => {
|
||||
if (rank === 1) return 'gold';
|
||||
|
||||
Reference in New Issue
Block a user