- <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>
|