国外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.

381 lines
12 KiB

1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
  1. <script setup lang="ts">
  2. import { useI18n } from 'vue-i18n';
  3. import { ref, onMounted, reactive } from 'vue';
  4. import { queryCourseList } from '@/api';
  5. import type { CourseItem } from '@/api';
  6. import { useConfig } from '@/utils/config';
  7. const { getConfigImage } = useConfig();
  8. const { t } = useI18n();
  9. // 里程碑数据 - 使用API返回的真实数据
  10. const milestones = ref<CourseItem[]>([]);
  11. const loading = ref(true);
  12. const error = ref<string | null>(null);
  13. // 为里程碑分配布局和样式
  14. const getMilestoneStyle = (index: number) => {
  15. const layouts = ['left', 'right', 'center'];
  16. const styles = ['large', 'medium', 'full'];
  17. const overlayColors = [
  18. 'from-indigo-900/80 to-transparent',
  19. 'from-emerald-900/80 to-transparent',
  20. 'from-purple-900/70 via-purple-900/40 to-transparent',
  21. 'from-blue-900/80 to-transparent',
  22. 'from-gray-900/80 to-transparent',
  23. 'from-amber-900/70 via-amber-900/40 to-transparent'
  24. ];
  25. const bgColors = [
  26. 'bg-gradient-to-br from-black to-indigo-900/30',
  27. 'bg-gradient-to-tl from-black to-emerald-900/30',
  28. 'bg-gradient-to-r from-black via-purple-900/20 to-black',
  29. 'bg-gradient-to-br from-black to-blue-900/30',
  30. 'bg-gradient-to-tl from-black to-gray-900/30',
  31. 'bg-gradient-to-r from-black via-amber-900/20 to-black'
  32. ];
  33. return {
  34. layout: layouts[index % layouts.length],
  35. style: styles[index % styles.length],
  36. overlayColor: overlayColors[index % overlayColors.length],
  37. bgColor: bgColors[index % bgColors.length]
  38. };
  39. };
  40. // 获取里程碑数据
  41. const fetchMilestones = async () => {
  42. loading.value = true;
  43. error.value = null;
  44. try {
  45. const response = await queryCourseList();
  46. if (response && Array.isArray(response) && response.length > 0) {
  47. milestones.value = response;
  48. }
  49. } catch (err) {
  50. console.error('获取发展历程数据失败:', err);
  51. error.value = '获取发展历程数据失败';
  52. } finally {
  53. loading.value = false;
  54. }
  55. };
  56. // 视差滚动效果
  57. const parallaxOffset = ref({ x: 0, y: 0 });
  58. // 鼠标移动事件处理
  59. const handleMouseMove = (event: MouseEvent) => {
  60. const x = (event.clientX / window.innerWidth - 0.5) * 20;
  61. const y = (event.clientY / window.innerHeight - 0.5) * 20;
  62. parallaxOffset.value = { x, y };
  63. };
  64. // 滚动到下一个里程碑
  65. const scrollToNext = (currentIndex: number) => {
  66. const nextElement = document.querySelectorAll('.milestone-section')[currentIndex + 1];
  67. if (nextElement) {
  68. nextElement.scrollIntoView({ behavior: 'smooth' });
  69. }
  70. };
  71. onMounted(() => {
  72. fetchMilestones();
  73. // 初始化WOW.js动画
  74. try {
  75. const WOW = (window as any).WOW;
  76. if (WOW) {
  77. new WOW({
  78. boxClass: 'wow',
  79. animateClass: 'animate__animated',
  80. offset: 100,
  81. mobile: true,
  82. live: true
  83. }).init();
  84. }
  85. } catch (error) {
  86. console.error('Failed to initialize WOW.js:', error);
  87. }
  88. // 监听鼠标移动
  89. window.addEventListener('mousemove', handleMouseMove);
  90. });
  91. </script>
  92. <template>
  93. <div class="milestone-gallery">
  94. <!-- 加载状态 -->
  95. <div v-if="loading" class="h-screen flex items-center justify-center">
  96. <div class="text-center">
  97. <div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-primary mx-auto mb-4"></div>
  98. <p class="text-text-secondary">加载里程碑数据中...</p>
  99. </div>
  100. </div>
  101. <!-- 错误状态 -->
  102. <div v-else-if="error" class="h-screen flex items-center justify-center">
  103. <div class="text-center">
  104. <p class="text-red-500 mb-4">{{ error }}</p>
  105. <button @click="fetchMilestones" class="px-6 py-3 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors">
  106. 重新加载
  107. </button>
  108. </div>
  109. </div>
  110. <!-- 里程碑展示 -->
  111. <div
  112. v-else
  113. v-for="(milestone, index) in milestones"
  114. :key="milestone.id"
  115. class="milestone-section relative w-full overflow-hidden"
  116. :class="[
  117. getMilestoneStyle(index).bgColor,
  118. getMilestoneStyle(index).style === 'full' ? 'h-screen' : (getMilestoneStyle(index).style === 'large' ? 'h-[85vh]' : 'h-[70vh]')
  119. ]"
  120. >
  121. <!-- 图片容器 - 视差效果 -->
  122. <div class="absolute inset-0 w-full h-full overflow-hidden">
  123. <!-- 图片 - 添加视差效果 -->
  124. <img
  125. :src="'/LOGO.png'"
  126. :alt="milestone.title"
  127. class="w-full h-full object-cover transition-transform duration-700 ease-out"
  128. :style="{
  129. transform: `scale(1.1) translate(${parallaxOffset.x * (index % 3 - 1) * 0.1}px, ${parallaxOffset.y * (index % 2 ? 1 : -1) * 0.1}px)`
  130. }"
  131. />
  132. <!-- 渐变叠加层 - 每个里程碑有不同的渐变 -->
  133. <div
  134. class="absolute inset-0 bg-gradient-to-r"
  135. :class="getMilestoneStyle(index).overlayColor"
  136. ></div>
  137. </div>
  138. <!-- 内容区域 - 根据布局调整位置 -->
  139. <div
  140. class="absolute inset-0 flex items-center"
  141. :class="{
  142. 'justify-start': getMilestoneStyle(index).layout === 'left',
  143. 'justify-end': getMilestoneStyle(index).layout === 'right',
  144. 'justify-center': getMilestoneStyle(index).layout === 'center'
  145. }"
  146. >
  147. <div
  148. class="max-w-xl p-8 md:p-16 backdrop-blur-sm bg-black/10 rounded-xl border border-white/10"
  149. :class="{
  150. 'ml-0 md:ml-16': getMilestoneStyle(index).layout === 'left',
  151. 'mr-0 md:mr-16': getMilestoneStyle(index).layout === 'right',
  152. 'mx-auto text-center': getMilestoneStyle(index).layout === 'center'
  153. }"
  154. >
  155. <!-- 年份编号 - 不同样式 -->
  156. <div
  157. class="mb-6"
  158. :class="{'flex items-center': getMilestoneStyle(index).layout !== 'center'}"
  159. >
  160. <div
  161. class="rounded-full backdrop-blur-sm flex items-center justify-center border border-white/30"
  162. :class="{
  163. 'w-16 h-16 bg-white/10': getMilestoneStyle(index).style !== 'full',
  164. 'w-20 h-20 bg-white/20': getMilestoneStyle(index).style === 'full',
  165. 'mx-auto': getMilestoneStyle(index).layout === 'center'
  166. }"
  167. >
  168. <span
  169. class="font-bold text-white"
  170. :class="{
  171. 'text-3xl': getMilestoneStyle(index).style !== 'full',
  172. 'text-4xl': getMilestoneStyle(index).style === 'full'
  173. }"
  174. >{{ index + 1 }}</span>
  175. </div>
  176. <div
  177. v-if="getMilestoneStyle(index).layout !== 'center'"
  178. class="ml-4 h-px bg-gradient-to-r from-white to-transparent flex-grow"
  179. ></div>
  180. </div>
  181. <!-- 标题 - 不同大小和动画 -->
  182. <h2
  183. class="font-bold text-white mb-6 wow animate__animated"
  184. :class="{
  185. 'text-4xl md:text-5xl': getMilestoneStyle(index).style === 'medium',
  186. 'text-5xl md:text-6xl': getMilestoneStyle(index).style === 'large',
  187. 'text-6xl md:text-7xl': getMilestoneStyle(index).style === 'full',
  188. 'animate__fadeInUp': getMilestoneStyle(index).layout === 'left' || getMilestoneStyle(index).layout === 'center',
  189. 'animate__fadeInRight': getMilestoneStyle(index).layout === 'right'
  190. }"
  191. >
  192. {{ milestone.title }}
  193. </h2>
  194. <!-- 描述 - 不同样式和动画 -->
  195. <div
  196. class="text-white/80 mb-8 wow animate__animated"
  197. :class="{
  198. 'text-base': getMilestoneStyle(index).style === 'medium',
  199. 'text-lg': getMilestoneStyle(index).style === 'large',
  200. 'text-xl': getMilestoneStyle(index).style === 'full',
  201. 'animate__fadeInUp animate__delay-xs': getMilestoneStyle(index).layout === 'left' || getMilestoneStyle(index).layout === 'center',
  202. 'animate__fadeInRight animate__delay-xs': getMilestoneStyle(index).layout === 'right'
  203. }"
  204. >
  205. <div v-html="milestone.description"></div>
  206. </div>
  207. <!-- 里程碑编号 -->
  208. <div
  209. class="flex items-center space-x-2 wow animate__animated animate__fadeInUp animate__delay-sm"
  210. :class="{
  211. 'justify-start': getMilestoneStyle(index).layout !== 'center',
  212. 'justify-center': getMilestoneStyle(index).layout === 'center'
  213. }"
  214. >
  215. <div class="w-3 h-3 rounded-full bg-white/40 animate-pulse"></div>
  216. <span class="text-white/70 text-sm">里程碑 {{ index + 1 }}/{{ milestones.length }}</span>
  217. </div>
  218. </div>
  219. </div>
  220. <!-- 装饰元素 - 随机位置 -->
  221. <div
  222. class="absolute"
  223. :class="{
  224. 'bottom-8 right-8': index % 3 === 0,
  225. 'top-8 right-8': index % 3 === 1,
  226. 'bottom-8 left-8': index % 3 === 2
  227. }"
  228. >
  229. <div class="flex items-center space-x-4">
  230. <div class="w-3 h-3 rounded-full bg-white/40 animate-pulse"></div>
  231. <div class="w-3 h-3 rounded-full bg-white/60 animate-pulse" style="animation-delay: 0.5s"></div>
  232. <div class="w-3 h-3 rounded-full bg-white/80 animate-pulse" style="animation-delay: 1s"></div>
  233. </div>
  234. </div>
  235. <!-- 浮动装饰 -->
  236. <div
  237. v-if="index % 2 === 0"
  238. class="absolute top-1/4 right-1/4 w-32 h-32 rounded-full border border-white/20 opacity-50 animate-float"
  239. :style="{animationDelay: `${index * 0.2}s`}"
  240. ></div>
  241. <div
  242. v-if="index % 2 === 1"
  243. class="absolute bottom-1/4 left-1/4 w-24 h-24 rounded-full border border-white/20 opacity-50 animate-float-reverse"
  244. :style="{animationDelay: `${index * 0.2}s`}"
  245. ></div>
  246. <!-- 滚动指示器 (除了最后一个) -->
  247. <div
  248. v-if="index < milestones.length - 1"
  249. 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"
  250. @click="scrollToNext(index)"
  251. >
  252. <span class="mb-1">继续探索</span>
  253. <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">
  254. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
  255. </svg>
  256. </div>
  257. </div>
  258. </div>
  259. </template>
  260. <style scoped>
  261. .milestone-gallery {
  262. scroll-snap-type: y proximity;
  263. overflow-y: auto;
  264. scroll-behavior: smooth;
  265. }
  266. .milestone-section {
  267. scroll-snap-align: start;
  268. position: relative;
  269. }
  270. /* 动画延迟类 */
  271. .animate__delay-xs {
  272. animation-delay: 0.2s;
  273. }
  274. .animate__delay-sm {
  275. animation-delay: 0.4s;
  276. }
  277. .animate__delay-md {
  278. animation-delay: 0.6s;
  279. }
  280. /* 脉冲动画 */
  281. @keyframes pulse {
  282. 0%, 100% {
  283. opacity: 0.6;
  284. transform: scale(1);
  285. }
  286. 50% {
  287. opacity: 1;
  288. transform: scale(1.2);
  289. }
  290. }
  291. .animate-pulse {
  292. animation: pulse 2s infinite;
  293. }
  294. /* 更柔和的弹跳动画 */
  295. @keyframes bounce-soft {
  296. 0%, 20%, 50%, 80%, 100% {
  297. transform: translateY(0);
  298. }
  299. 40% {
  300. transform: translateY(-8px);
  301. }
  302. 60% {
  303. transform: translateY(-4px);
  304. }
  305. }
  306. .animate-bounce-soft {
  307. animation: bounce-soft 2s infinite;
  308. }
  309. /* 浮动动画 */
  310. @keyframes float {
  311. 0%, 100% {
  312. transform: translateY(0) translateX(0);
  313. }
  314. 25% {
  315. transform: translateY(-10px) translateX(5px);
  316. }
  317. 50% {
  318. transform: translateY(0) translateX(10px);
  319. }
  320. 75% {
  321. transform: translateY(10px) translateX(5px);
  322. }
  323. }
  324. .animate-float {
  325. animation: float 8s ease-in-out infinite;
  326. }
  327. @keyframes float-reverse {
  328. 0%, 100% {
  329. transform: translateY(0) translateX(0);
  330. }
  331. 25% {
  332. transform: translateY(10px) translateX(-5px);
  333. }
  334. 50% {
  335. transform: translateY(0) translateX(-10px);
  336. }
  337. 75% {
  338. transform: translateY(-10px) translateX(-5px);
  339. }
  340. }
  341. .animate-float-reverse {
  342. animation: float-reverse 8s ease-in-out infinite;
  343. }
  344. </style>