hookehuyr

feat(recall): 添加垂直滑动页面和动画效果

为回忆页面添加垂直滑动功能,使用Swiper实现多屏滑动效果
添加元素进入动画和过渡效果,提升用户体验
新增第二屏内容展示用户活动数据和义工信息
1 <!-- 1 <!--
2 * @Date: 2025-12-19 15:40:34 2 * @Date: 2025-12-19 15:40:34
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-19 16:00:08 4 + * @LastEditTime: 2025-12-19 16:24:51
5 * @FilePath: /mlaj/src/views/recall/Index.vue 5 * @FilePath: /mlaj/src/views/recall/Index.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
8 <template> 8 <template>
9 - <div class="min-h-screen bg-[#F0FBF9] flex flex-col items-center pt-20 pb-10 px-6 relative overflow-hidden"> 9 + <div class="h-screen bg-[#F0FBF9] overflow-hidden relative">
10 + <!-- Swiper Container -->
11 + <swiper
12 + :direction="'vertical'"
13 + :modules="[Mousewheel]"
14 + :mousewheel="true"
15 + class="h-full w-full"
16 + @slideChange="onSlideChange"
17 + :speed="800"
18 + @swiper="onSwiper"
19 + >
20 + <!-- 第一屏 -->
21 + <swiper-slide>
22 + <div class="flex flex-col items-center pt-20 pb-10 px-6 relative h-full w-full">
10 <!-- 头像 --> 23 <!-- 头像 -->
11 - <div class="relative mb-6"> 24 + <div class="relative mb-6 opacity-0 translate-y-4 transition-all duration-700 delay-100" :class="{ 'opacity-100 translate-y-0': activeIndex === 0 }">
12 <img 25 <img
13 :src="userAvatar || defaultAvatar" 26 :src="userAvatar || defaultAvatar"
14 class="w-24 h-24 rounded-full border-4 border-white shadow-lg object-cover" 27 class="w-24 h-24 rounded-full border-4 border-white shadow-lg object-cover"
...@@ -17,12 +30,12 @@ ...@@ -17,12 +30,12 @@
17 </div> 30 </div>
18 31
19 <!-- 欢迎语 --> 32 <!-- 欢迎语 -->
20 - <h1 class="text-2xl font-bold text-[#1A3B34] mb-2">{{ userName }}</h1> 33 + <h1 class="text-2xl font-bold text-[#1A3B34] mb-2 text-center opacity-0 translate-y-4 transition-all duration-700 delay-200" :class="{ 'opacity-100 translate-y-0': activeIndex === 0 }">{{ userName }}</h1>
21 - <h2 class="text-xl font-bold text-[#1A3B34] mb-4">欢迎回归美乐爱觉宇宙</h2> 34 + <h2 class="text-xl font-bold text-[#1A3B34] mb-4 text-center opacity-0 translate-y-4 transition-all duration-700 delay-300" :class="{ 'opacity-100 translate-y-0': activeIndex === 0 }">欢迎回归美乐爱觉宇宙</h2>
22 - <p class="text-slate-500 text-sm mb-12">您与Behalo的故事从这里开始</p> 35 + <p class="text-slate-500 text-sm mb-12 text-center opacity-0 translate-y-4 transition-all duration-700 delay-400" :class="{ 'opacity-100 translate-y-0': activeIndex === 0 }">您与Behalo的故事从这里开始</p>
23 36
24 <!-- 时间卡片 --> 37 <!-- 时间卡片 -->
25 - <div class="bg-white rounded-3xl p-8 shadow-xl w-full max-w-sm text-center mb-auto relative z-10"> 38 + <div class="bg-white rounded-3xl p-8 shadow-xl w-full max-w-sm text-center mb-auto relative z-10 opacity-0 scale-95 transition-all duration-700 delay-500" :class="{ 'opacity-100 scale-100': activeIndex === 0 }">
26 <p class="text-slate-500 mb-4">您加入Behalo的时间</p> 39 <p class="text-slate-500 mb-4">您加入Behalo的时间</p>
27 <div class="text-3xl font-bold text-[#1A3B34] mb-4 tracking-wide">{{ joinDateFormatted }}</div> 40 <div class="text-3xl font-bold text-[#1A3B34] mb-4 tracking-wide">{{ joinDateFormatted }}</div>
28 <div class="text-slate-500"> 41 <div class="text-slate-500">
...@@ -31,24 +44,107 @@ ...@@ -31,24 +44,107 @@
31 </div> 44 </div>
32 45
33 <!-- 底部引导 --> 46 <!-- 底部引导 -->
34 - <div class="flex flex-col items-center mt-12 animate-bounce cursor-pointer"> 47 + <div class="flex flex-col items-center mt-12 animate-bounce cursor-pointer opacity-0 transition-opacity duration-1000 delay-1000" :class="{ 'opacity-100': activeIndex === 0 }" @click="slideNext">
35 <ChevronDoubleDownIcon class="w-6 h-6 text-[#4ADE80] mb-2" /> 48 <ChevronDoubleDownIcon class="w-6 h-6 text-[#4ADE80] mb-2" />
36 <span class="text-[#4ADE80] font-medium text-sm">向上滑动,探索更多历程</span> 49 <span class="text-[#4ADE80] font-medium text-sm">向上滑动,探索更多历程</span>
37 </div> 50 </div>
38 </div> 51 </div>
52 + </swiper-slide>
53 +
54 + <!-- 第二屏 -->
55 + <swiper-slide>
56 + <div class="flex flex-col items-center pt-10 pb-10 px-6 relative h-full w-full overflow-y-auto overflow-x-hidden">
57 + <h2 class="text-2xl font-bold text-[#1A3B34] mb-2 text-center opacity-0 translate-y-4 transition-all duration-700 delay-100" :class="{ 'opacity-100 translate-y-0': activeIndex === 1 }">您的Behalo足迹</h2>
58 + <p class="text-slate-500 text-sm mb-8 text-center opacity-0 translate-y-4 transition-all duration-700 delay-200" :class="{ 'opacity-100 translate-y-0': activeIndex === 1 }">2016~2025,您在Behalo留下的印记</p>
59 +
60 + <!-- 活动参与卡片 -->
61 + <div class="bg-white rounded-3xl p-6 shadow-md w-full max-w-sm mb-4 opacity-0 translate-y-4 transition-all duration-700 delay-300" :class="{ 'opacity-100 translate-y-0': activeIndex === 1 }">
62 + <div class="flex items-center mb-4">
63 + <div class="w-10 h-10 rounded-full bg-[#E0F2F1] flex items-center justify-center mr-3 text-[#26A69A]">
64 + <CalendarIcon class="w-5 h-5" />
65 + </div>
66 + <span class="font-bold text-[#1A3B34]">参与宇宙活动</span>
67 + </div>
68 + <div class="mb-2">
69 + <span class="text-5xl font-bold text-[#1A3B34]">{{ activityCount }}</span>
70 + <span class="text-slate-500 ml-1">次</span>
71 + </div>
72 + <p class="text-slate-500 text-xs">您积极参与各类教育活动,累计参与{{ activityCount }}次</p>
73 + </div>
74 +
75 + <!-- 义工服务卡片 -->
76 + <div class="bg-white rounded-3xl p-6 shadow-md w-full max-w-sm mb-8 opacity-0 translate-y-4 transition-all duration-700 delay-500" :class="{ 'opacity-100 translate-y-0': activeIndex === 1 }">
77 + <div class="flex items-center mb-4">
78 + <div class="w-10 h-10 rounded-full bg-[#E8F5E9] flex items-center justify-center mr-3 text-[#4CAF50]">
79 + <HeartIcon class="w-5 h-5" />
80 + </div>
81 + <span class="font-bold text-[#1A3B34]">义工服务活动</span>
82 + </div>
83 + <div class="mb-2">
84 + <span class="text-5xl font-bold text-[#1A3B34]">{{ volunteerCount }}</span>
85 + <span class="text-slate-500 ml-1">次</span>
86 + </div>
87 + <p class="text-slate-500 text-xs">您热心公益,作为义工服务了{{ volunteerCount }}次活动,贡献时长超过{{ volunteerHours }}小时</p>
88 + </div>
89 +
90 + <!-- 荣誉徽章 -->
91 + <!--<div class="w-full max-w-sm opacity-0 translate-y-4 transition-all duration-700 delay-700" :class="{ 'opacity-100 translate-y-0': activeIndex === 1 }">
92 + <h3 class="font-bold text-[#1A3B34] mb-4">获得的荣誉徽章</h3>
93 + <div class="flex justify-between px-2">
94 + <!~~ 徽章1 ~~>
95 + <div class="w-16 h-16 rounded-full bg-[#FFF9C4] flex items-center justify-center shadow-sm">
96 + <TrophyIcon class="w-8 h-8 text-[#FBC02D]" />
97 + </div>
98 + <!~~ 徽章2 ~~>
99 + <div class="w-16 h-16 rounded-full bg-[#F3E5F5] flex items-center justify-center shadow-sm">
100 + <StarIcon class="w-8 h-8 text-[#AB47BC]" />
101 + </div>
102 + <!~~ 徽章3 ~~>
103 + <div class="w-16 h-16 rounded-full bg-[#E3F2FD] flex items-center justify-center shadow-sm">
104 + <CheckBadgeIcon class="w-8 h-8 text-[#42A5F5]" />
105 + </div>
106 + <!~~ 徽章4 (锁定) ~~>
107 + <div class="w-16 h-16 rounded-full bg-[#F5F5F5] flex items-center justify-center shadow-sm">
108 + <LockClosedIcon class="w-8 h-8 text-[#BDBDBD]" />
109 + </div>
110 + </div>
111 + </div>-->
112 + </div>
113 + </swiper-slide>
114 + </swiper>
115 + </div>
39 </template> 116 </template>
40 117
41 <script setup> 118 <script setup>
42 -import { computed, onMounted } from 'vue'; 119 +import { computed, onMounted, ref } from 'vue';
43 import { useAuth } from '@/contexts/auth'; 120 import { useAuth } from '@/contexts/auth';
44 -import { ChevronDoubleDownIcon } from '@heroicons/vue/24/solid'; 121 +import { ChevronDoubleDownIcon, CalendarIcon, HeartIcon, TrophyIcon, StarIcon, CheckBadgeIcon, LockClosedIcon } from '@heroicons/vue/24/solid'; // Added icons
45 import dayjs from 'dayjs'; 122 import dayjs from 'dayjs';
123 +import { Swiper, SwiperSlide } from 'swiper/vue';
124 +import { Mousewheel } from 'swiper/modules';
125 +import 'swiper/css';
46 126
47 // 状态 127 // 状态
48 const { currentUser } = useAuth(); 128 const { currentUser } = useAuth();
49 // 使用一个通用的默认头像URL,实际项目中应替换为本地资源或配置的默认图 129 // 使用一个通用的默认头像URL,实际项目中应替换为本地资源或配置的默认图
50 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/default_avatar.png'; 130 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/default_avatar.png';
51 131
132 +// Swiper 实例和状态
133 +const swiperInstance = ref(null);
134 +const activeIndex = ref(0);
135 +
136 +const onSwiper = (swiper) => {
137 + swiperInstance.value = swiper;
138 +};
139 +
140 +const onSlideChange = (swiper) => {
141 + activeIndex.value = swiper.activeIndex;
142 +};
143 +
144 +const slideNext = () => {
145 + swiperInstance.value?.slideNext();
146 +};
147 +
52 const userAvatar = computed(() => { 148 const userAvatar = computed(() => {
53 let avatar = currentUser.value?.avatar; 149 let avatar = currentUser.value?.avatar;
54 if (avatar && avatar.includes('cdn.ipadbiz.cn')) { 150 if (avatar && avatar.includes('cdn.ipadbiz.cn')) {
...@@ -60,7 +156,6 @@ const userAvatar = computed(() => { ...@@ -60,7 +156,6 @@ const userAvatar = computed(() => {
60 const userName = computed(() => currentUser.value?.name || '旅行者'); 156 const userName = computed(() => currentUser.value?.name || '旅行者');
61 157
62 // 加入时间逻辑 158 // 加入时间逻辑
63 -// 优先使用 created_at,如果没有则尝试 reg_time,最后回退到当前时间(防止报错)
64 const joinDate = computed(() => { 159 const joinDate = computed(() => {
65 return currentUser.value?.created_at || currentUser.value?.reg_time || new Date(); 160 return currentUser.value?.created_at || currentUser.value?.reg_time || new Date();
66 }); 161 });
...@@ -75,7 +170,6 @@ const durationString = computed(() => { ...@@ -75,7 +170,6 @@ const durationString = computed(() => {
75 const years = now.diff(start, 'year'); 170 const years = now.diff(start, 'year');
76 const months = now.diff(start, 'month') % 12; 171 const months = now.diff(start, 'month') % 12;
77 172
78 - // 如果不满一个月,显示天数
79 if (years === 0 && months === 0) { 173 if (years === 0 && months === 0) {
80 const days = now.diff(start, 'day'); 174 const days = now.diff(start, 'day');
81 return `${days} 天`; 175 return `${days} 天`;
...@@ -88,8 +182,13 @@ const durationString = computed(() => { ...@@ -88,8 +182,13 @@ const durationString = computed(() => {
88 } 182 }
89 }); 183 });
90 184
185 +// Mock Data for Second Screen
186 +const activityCount = ref(28);
187 +const volunteerCount = ref(12);
188 +const volunteerHours = ref(100);
189 +
190 +
91 onMounted(() => { 191 onMounted(() => {
92 - // 调试用,确认用户信息字段
93 console.log('Recall Page - Current User:', currentUser.value); 192 console.log('Recall Page - Current User:', currentUser.value);
94 }); 193 });
95 </script> 194 </script>
......