国外MOSE官网
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

382 lines
12 KiB

<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { ref, onMounted, reactive } from 'vue';
import { queryCourseList } from '@/api';
import type { CourseItem } from '@/api';
import { useConfig } from '@/utils/config';
const { getConfigImage } = useConfig();
const { t } = useI18n();
// 里程碑数据 - 使用API返回的真实数据
const milestones = ref<CourseItem[]>([]);
const loading = ref(true);
const error = ref<string | null>(null);
// 为里程碑分配布局和样式
const getMilestoneStyle = (index: number) => {
const layouts = ['left', 'right', 'center'];
const styles = ['large', 'medium', 'full'];
const overlayColors = [
'from-indigo-900/80 to-transparent',
'from-emerald-900/80 to-transparent',
'from-purple-900/70 via-purple-900/40 to-transparent',
'from-blue-900/80 to-transparent',
'from-gray-900/80 to-transparent',
'from-amber-900/70 via-amber-900/40 to-transparent'
];
const bgColors = [
'bg-gradient-to-br from-black to-indigo-900/30',
'bg-gradient-to-tl from-black to-emerald-900/30',
'bg-gradient-to-r from-black via-purple-900/20 to-black',
'bg-gradient-to-br from-black to-blue-900/30',
'bg-gradient-to-tl from-black to-gray-900/30',
'bg-gradient-to-r from-black via-amber-900/20 to-black'
];
return {
layout: layouts[index % layouts.length],
style: styles[index % styles.length],
overlayColor: overlayColors[index % overlayColors.length],
bgColor: bgColors[index % bgColors.length]
};
};
// 获取里程碑数据
const fetchMilestones = async () => {
loading.value = true;
error.value = null;
try {
const response = await queryCourseList();
if (response && Array.isArray(response) && response.length > 0) {
milestones.value = response;
}
} catch (err) {
console.error('获取发展历程数据失败:', err);
error.value = '获取发展历程数据失败';
} finally {
loading.value = false;
}
};
// 视差滚动效果
const parallaxOffset = ref({ x: 0, y: 0 });
// 鼠标移动事件处理
const handleMouseMove = (event: MouseEvent) => {
const x = (event.clientX / window.innerWidth - 0.5) * 20;
const y = (event.clientY / window.innerHeight - 0.5) * 20;
parallaxOffset.value = { x, y };
};
// 滚动到下一个里程碑
const scrollToNext = (currentIndex: number) => {
const nextElement = document.querySelectorAll('.milestone-section')[currentIndex + 1];
if (nextElement) {
nextElement.scrollIntoView({ behavior: 'smooth' });
}
};
onMounted(() => {
fetchMilestones();
// 初始化WOW.js动画
try {
const WOW = (window as any).WOW;
if (WOW) {
new WOW({
boxClass: 'wow',
animateClass: 'animate__animated',
offset: 100,
mobile: true,
live: true
}).init();
}
} catch (error) {
console.error('Failed to initialize WOW.js:', error);
}
// 监听鼠标移动
window.addEventListener('mousemove', handleMouseMove);
});
</script>
<template>
<div class="milestone-gallery">
<!-- 加载状态 -->
<div v-if="loading" class="h-screen flex items-center justify-center">
<div class="text-center">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-primary mx-auto mb-4"></div>
<p class="text-text-secondary">加载里程碑数据中...</p>
</div>
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="h-screen flex items-center justify-center">
<div class="text-center">
<p class="text-red-500 mb-4">{{ error }}</p>
<button @click="fetchMilestones" class="px-6 py-3 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors">
重新加载
</button>
</div>
</div>
<!-- 里程碑展示 -->
<div
v-else
v-for="(milestone, index) in milestones"
:key="milestone.id"
class="milestone-section relative w-full overflow-hidden"
:class="[
getMilestoneStyle(index).bgColor,
getMilestoneStyle(index).style === 'full' ? 'h-screen' : (getMilestoneStyle(index).style === 'large' ? 'h-[85vh]' : 'h-[70vh]')
]"
>
<!-- 图片容器 - 视差效果 -->
<div class="absolute inset-0 w-full h-full overflow-hidden">
<!-- 图片 - 添加视差效果 -->
<img
:src="'/LOGO.png'"
:alt="milestone.title"
class="w-full h-full object-cover transition-transform duration-700 ease-out"
:style="{
transform: `scale(1.1) translate(${parallaxOffset.x * (index % 3 - 1) * 0.1}px, ${parallaxOffset.y * (index % 2 ? 1 : -1) * 0.1}px)`
}"
/>
<!-- 渐变叠加层 - 每个里程碑有不同的渐变 -->
<div
class="absolute inset-0 bg-gradient-to-r"
:class="getMilestoneStyle(index).overlayColor"
></div>
</div>
<!-- 内容区域 - 根据布局调整位置 -->
<div
class="absolute inset-0 flex items-center"
:class="{
'justify-start': getMilestoneStyle(index).layout === 'left',
'justify-end': getMilestoneStyle(index).layout === 'right',
'justify-center': getMilestoneStyle(index).layout === 'center'
}"
>
<div
class="max-w-xl p-8 md:p-16 backdrop-blur-sm bg-black/10 rounded-xl border border-white/10"
:class="{
'ml-0 md:ml-16': getMilestoneStyle(index).layout === 'left',
'mr-0 md:mr-16': getMilestoneStyle(index).layout === 'right',
'mx-auto text-center': getMilestoneStyle(index).layout === 'center'
}"
>
<!-- 年份编号 - 不同样式 -->
<div
class="mb-6"
:class="{'flex items-center': getMilestoneStyle(index).layout !== 'center'}"
>
<div
class="rounded-full backdrop-blur-sm flex items-center justify-center border border-white/30"
:class="{
'w-16 h-16 bg-white/10': getMilestoneStyle(index).style !== 'full',
'w-20 h-20 bg-white/20': getMilestoneStyle(index).style === 'full',
'mx-auto': getMilestoneStyle(index).layout === 'center'
}"
>
<span
class="font-bold text-white"
:class="{
'text-3xl': getMilestoneStyle(index).style !== 'full',
'text-4xl': getMilestoneStyle(index).style === 'full'
}"
>{{ index + 1 }}</span>
</div>
<div
v-if="getMilestoneStyle(index).layout !== 'center'"
class="ml-4 h-px bg-gradient-to-r from-white to-transparent flex-grow"
></div>
</div>
<!-- 标题 - 不同大小和动画 -->
<h2
class="font-bold text-white mb-6 wow animate__animated"
:class="{
'text-4xl md:text-5xl': getMilestoneStyle(index).style === 'medium',
'text-5xl md:text-6xl': getMilestoneStyle(index).style === 'large',
'text-6xl md:text-7xl': getMilestoneStyle(index).style === 'full',
'animate__fadeInUp': getMilestoneStyle(index).layout === 'left' || getMilestoneStyle(index).layout === 'center',
'animate__fadeInRight': getMilestoneStyle(index).layout === 'right'
}"
>
{{ milestone.title }}
</h2>
<!-- 描述 - 不同样式和动画 -->
<div
class="text-white/80 mb-8 wow animate__animated"
:class="{
'text-base': getMilestoneStyle(index).style === 'medium',
'text-lg': getMilestoneStyle(index).style === 'large',
'text-xl': getMilestoneStyle(index).style === 'full',
'animate__fadeInUp animate__delay-xs': getMilestoneStyle(index).layout === 'left' || getMilestoneStyle(index).layout === 'center',
'animate__fadeInRight animate__delay-xs': getMilestoneStyle(index).layout === 'right'
}"
>
<div v-html="milestone.description"></div>
</div>
<!-- 里程碑编号 -->
<div
class="flex items-center space-x-2 wow animate__animated animate__fadeInUp animate__delay-sm"
:class="{
'justify-start': getMilestoneStyle(index).layout !== 'center',
'justify-center': getMilestoneStyle(index).layout === 'center'
}"
>
<div class="w-3 h-3 rounded-full bg-white/40 animate-pulse"></div>
<span class="text-white/70 text-sm">里程碑 {{ index + 1 }}/{{ milestones.length }}</span>
</div>
</div>
</div>
<!-- 装饰元素 - 随机位置 -->
<div
class="absolute"
:class="{
'bottom-8 right-8': index % 3 === 0,
'top-8 right-8': index % 3 === 1,
'bottom-8 left-8': index % 3 === 2
}"
>
<div class="flex items-center space-x-4">
<div class="w-3 h-3 rounded-full bg-white/40 animate-pulse"></div>
<div class="w-3 h-3 rounded-full bg-white/60 animate-pulse" style="animation-delay: 0.5s"></div>
<div class="w-3 h-3 rounded-full bg-white/80 animate-pulse" style="animation-delay: 1s"></div>
</div>
</div>
<!-- 浮动装饰 -->
<div
v-if="index % 2 === 0"
class="absolute top-1/4 right-1/4 w-32 h-32 rounded-full border border-white/20 opacity-50 animate-float"
:style="{animationDelay: `${index * 0.2}s`}"
></div>
<div
v-if="index % 2 === 1"
class="absolute bottom-1/4 left-1/4 w-24 h-24 rounded-full border border-white/20 opacity-50 animate-float-reverse"
:style="{animationDelay: `${index * 0.2}s`}"
></div>
<!-- 滚动指示器 (除了最后一个) -->
<div
v-if="index < milestones.length - 1"
class="absolute bottom-8 left-1/2 transform -translate-x-1/2 text-white/70 text-sm flex flex-col items-center cursor-pointer hover:text-white transition-colors duration-300"
@click="scrollToNext(index)"
>
<span class="mb-1">继续探索</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 animate-bounce-soft" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg>
</div>
</div>
</div>
</template>
<style scoped>
.milestone-gallery {
scroll-snap-type: y proximity;
overflow-y: auto;
scroll-behavior: smooth;
}
.milestone-section {
scroll-snap-align: start;
position: relative;
}
/* 动画延迟类 */
.animate__delay-xs {
animation-delay: 0.2s;
}
.animate__delay-sm {
animation-delay: 0.4s;
}
.animate__delay-md {
animation-delay: 0.6s;
}
/* 脉冲动画 */
@keyframes pulse {
0%, 100% {
opacity: 0.6;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.2);
}
}
.animate-pulse {
animation: pulse 2s infinite;
}
/* 更柔和的弹跳动画 */
@keyframes bounce-soft {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-8px);
}
60% {
transform: translateY(-4px);
}
}
.animate-bounce-soft {
animation: bounce-soft 2s infinite;
}
/* 浮动动画 */
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
}
25% {
transform: translateY(-10px) translateX(5px);
}
50% {
transform: translateY(0) translateX(10px);
}
75% {
transform: translateY(10px) translateX(5px);
}
}
.animate-float {
animation: float 8s ease-in-out infinite;
}
@keyframes float-reverse {
0%, 100% {
transform: translateY(0) translateX(0);
}
25% {
transform: translateY(10px) translateX(-5px);
}
50% {
transform: translateY(0) translateX(-10px);
}
75% {
transform: translateY(-10px) translateX(-5px);
}
}
.animate-float-reverse {
animation: float-reverse 8s ease-in-out infinite;
}
</style>