feat(recall): 添加垂直滑动页面和动画效果
为回忆页面添加垂直滑动功能,使用Swiper实现多屏滑动效果 添加元素进入动画和过渡效果,提升用户体验 新增第二屏内容展示用户活动数据和义工信息
Showing
1 changed file
with
134 additions
and
35 deletions
| 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 | - <!-- 头像 --> | 10 | + <!-- Swiper Container --> |
| 11 | - <div class="relative mb-6"> | 11 | + <swiper |
| 12 | - <img | 12 | + :direction="'vertical'" |
| 13 | - :src="userAvatar || defaultAvatar" | 13 | + :modules="[Mousewheel]" |
| 14 | - class="w-24 h-24 rounded-full border-4 border-white shadow-lg object-cover" | 14 | + :mousewheel="true" |
| 15 | - alt="User Avatar" | 15 | + class="h-full w-full" |
| 16 | - /> | 16 | + @slideChange="onSlideChange" |
| 17 | - </div> | 17 | + :speed="800" |
| 18 | - | 18 | + @swiper="onSwiper" |
| 19 | - <!-- 欢迎语 --> | 19 | + > |
| 20 | - <h1 class="text-2xl font-bold text-[#1A3B34] mb-2">{{ userName }}</h1> | 20 | + <!-- 第一屏 --> |
| 21 | - <h2 class="text-xl font-bold text-[#1A3B34] mb-4">欢迎回归美乐爱觉宇宙</h2> | 21 | + <swiper-slide> |
| 22 | - <p class="text-slate-500 text-sm mb-12">您与Behalo的故事从这里开始</p> | 22 | + <div class="flex flex-col items-center pt-20 pb-10 px-6 relative h-full w-full"> |
| 23 | - | 23 | + <!-- 头像 --> |
| 24 | - <!-- 时间卡片 --> | 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 }"> |
| 25 | - <div class="bg-white rounded-3xl p-8 shadow-xl w-full max-w-sm text-center mb-auto relative z-10"> | 25 | + <img |
| 26 | - <p class="text-slate-500 mb-4">您加入Behalo的时间</p> | 26 | + :src="userAvatar || defaultAvatar" |
| 27 | - <div class="text-3xl font-bold text-[#1A3B34] mb-4 tracking-wide">{{ joinDateFormatted }}</div> | 27 | + class="w-24 h-24 rounded-full border-4 border-white shadow-lg object-cover" |
| 28 | - <div class="text-slate-500"> | 28 | + alt="User Avatar" |
| 29 | - 已有 <span class="text-[#1A3B34] font-bold text-lg">{{ durationString }}</span> | 29 | + /> |
| 30 | - </div> | 30 | + </div> |
| 31 | - </div> | 31 | + |
| 32 | - | 32 | + <!-- 欢迎语 --> |
| 33 | - <!-- 底部引导 --> | 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> |
| 34 | - <div class="flex flex-col items-center mt-12 animate-bounce cursor-pointer"> | 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> |
| 35 | - <ChevronDoubleDownIcon class="w-6 h-6 text-[#4ADE80] mb-2" /> | 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> |
| 36 | - <span class="text-[#4ADE80] font-medium text-sm">向上滑动,探索更多历程</span> | 36 | + |
| 37 | - </div> | 37 | + <!-- 时间卡片 --> |
| 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 }"> | ||
| 39 | + <p class="text-slate-500 mb-4">您加入Behalo的时间</p> | ||
| 40 | + <div class="text-3xl font-bold text-[#1A3B34] mb-4 tracking-wide">{{ joinDateFormatted }}</div> | ||
| 41 | + <div class="text-slate-500"> | ||
| 42 | + 已有 <span class="text-[#1A3B34] font-bold text-lg">{{ durationString }}</span> | ||
| 43 | + </div> | ||
| 44 | + </div> | ||
| 45 | + | ||
| 46 | + <!-- 底部引导 --> | ||
| 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"> | ||
| 48 | + <ChevronDoubleDownIcon class="w-6 h-6 text-[#4ADE80] mb-2" /> | ||
| 49 | + <span class="text-[#4ADE80] font-medium text-sm">向上滑动,探索更多历程</span> | ||
| 50 | + </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> | ||
| 38 | </div> | 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> | ... | ... |
-
Please register or login to post a comment