feat(recall): 添加时光机旅程页面和路由配置
- 新增时光机旅程页面组件,包含欢迎和足迹两个垂直滑动页面 - 添加相关图片资源文件 - 更新路由配置,修改路由名称和页面标题 - 实现用户加入时间计算和活动统计展示功能
Showing
5 changed files
with
229 additions
and
2 deletions
src/assets/images/recall/title03@2x.png
0 → 100644
128 KB
src/assets/images/recall/title04@2x.png
0 → 100644
98.5 KB
src/assets/images/recall/xia@2x.png
0 → 100644
983 Bytes
| ... | @@ -128,10 +128,11 @@ export const routes = [ | ... | @@ -128,10 +128,11 @@ export const routes = [ |
| 128 | }, | 128 | }, |
| 129 | { | 129 | { |
| 130 | path: '/recall/timeline', | 130 | path: '/recall/timeline', |
| 131 | - name: 'Timeline', | 131 | + name: 'RecallTimeline', |
| 132 | component: () => import('../views/recall/timeline.vue'), | 132 | component: () => import('../views/recall/timeline.vue'), |
| 133 | - meta: { title: '加入时间/活动记录' }, | 133 | + meta: { title: '时光机旅程' }, |
| 134 | }, | 134 | }, |
| 135 | + | ||
| 135 | { | 136 | { |
| 136 | path: '/checkout', | 137 | path: '/checkout', |
| 137 | name: 'CheckoutPage', | 138 | name: 'CheckoutPage', | ... | ... |
src/views/recall/timeline.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="timeline-page w-full h-screen relative overflow-hidden"> | ||
| 3 | + <!-- Shared Background --> | ||
| 4 | + <StarryBackground :bg-image="bgImg" class="absolute inset-0 z-0" /> | ||
| 5 | + | ||
| 6 | + <!-- Swiper --> | ||
| 7 | + <swiper :direction="'vertical'" :modules="[Mousewheel]" :mousewheel="true" :speed="800" | ||
| 8 | + class="h-full w-full relative z-10" @swiper="onSwiper" @slideChange="onSlideChange"> | ||
| 9 | + <!-- Slide 1: Welcome Back --> | ||
| 10 | + <swiper-slide> | ||
| 11 | + <div class="flex flex-col items-center h-full w-full px-6 pt-16 pb-8 relative"> | ||
| 12 | + | ||
| 13 | + <!-- Title Section --> | ||
| 14 | + <div class="flex flex-col items-center mb-8 animate-fade-in-down"> | ||
| 15 | + <h2 class="text-[#FFDD01] text-2xl font-bold mb-4 tracking-wider">@{{ userName }}</h2> | ||
| 16 | + <img :src="title03" class="w-64 object-contain" alt="Welcome Back" /> | ||
| 17 | + </div> | ||
| 18 | + | ||
| 19 | + <!-- Card Section --> | ||
| 20 | + <FrostedGlass | ||
| 21 | + class="w-full max-w-sm p-8 !rounded-2xl !border-white/20 flex flex-col items-center justify-center text-center mb-auto animate-zoom-in" | ||
| 22 | + :bg-opacity="15" blur-level="md"> | ||
| 23 | + <p class="text-white text-sm mb-6 tracking-wide opacity-90">您加入BEHALO星球的时间</p> | ||
| 24 | + <h1 class="text-[#FFDD01] text-3xl font-bold mb-4 tracking-wider">{{ joinDateFormatted }}</h1> | ||
| 25 | + <p class="text-white text-lg tracking-wide font-medium">已有{{ durationString }}</p> | ||
| 26 | + </FrostedGlass> | ||
| 27 | + | ||
| 28 | + <!-- Bottom Section --> | ||
| 29 | + <div class="mt-auto flex flex-col items-center text-center animate-fade-in-up"> | ||
| 30 | + <p class="text-white/80 text-xs mb-2 tracking-wider leading-relaxed"> | ||
| 31 | + 从{{ joinYear }}年起,每一段旅程都被珍藏。<br> | ||
| 32 | + 欢迎回家,继续书写我们共有的星球诗篇。 | ||
| 33 | + </p> | ||
| 34 | + <div class="mt-4 cursor-pointer animate-bounce" @click="slideNext"> | ||
| 35 | + <img :src="arrowDown" class="w-6 h-6" alt="Scroll Down" /> | ||
| 36 | + </div> | ||
| 37 | + </div> | ||
| 38 | + | ||
| 39 | + </div> | ||
| 40 | + </swiper-slide> | ||
| 41 | + | ||
| 42 | + <!-- Slide 2: Footprints --> | ||
| 43 | + <swiper-slide> | ||
| 44 | + <div class="flex flex-col items-center h-full w-full px-6 pt-16 pb-8 relative"> | ||
| 45 | + | ||
| 46 | + <!-- Title Section --> | ||
| 47 | + <div class="flex flex-col items-center mb-10 animate-fade-in-down"> | ||
| 48 | + <img :src="title04" class="w-64 object-contain mb-4" alt="My Footprints" /> | ||
| 49 | + <h2 class="text-[#FFDD01] text-2xl font-bold tracking-wider">@{{ userName }}</h2> | ||
| 50 | + </div> | ||
| 51 | + | ||
| 52 | + <!-- Card Section --> | ||
| 53 | + <FrostedGlass | ||
| 54 | + class="w-full max-w-sm p-8 !rounded-2xl !border-white/20 flex flex-col items-center justify-center text-center mb-auto animate-zoom-in delay-100" | ||
| 55 | + :bg-opacity="15" blur-level="md"> | ||
| 56 | + | ||
| 57 | + <div class="flex flex-col items-center mb-8"> | ||
| 58 | + <p class="text-white text-sm mb-2 tracking-wide opacity-90">参与星球活动</p> | ||
| 59 | + <h1 class="text-[#FFDD01] text-4xl font-bold tracking-wider">{{ activityCount }}次</h1> | ||
| 60 | + </div> | ||
| 61 | + | ||
| 62 | + <div class="flex flex-col items-center"> | ||
| 63 | + <p class="text-white text-sm mb-2 tracking-wide opacity-90">义工服务记录</p> | ||
| 64 | + <h1 class="text-[#FFDD01] text-4xl font-bold tracking-wider">{{ volunteerCount }}次</h1> | ||
| 65 | + </div> | ||
| 66 | + | ||
| 67 | + </FrostedGlass> | ||
| 68 | + | ||
| 69 | + <!-- Bottom Section --> | ||
| 70 | + <div class="mt-auto w-full flex flex-col items-center text-center animate-fade-in-up delay-200"> | ||
| 71 | + <p class="text-white/80 text-xs mb-6 tracking-wider leading-relaxed"> | ||
| 72 | + 每一次参与,<br> | ||
| 73 | + 都是这片星球上不灭的星光。 | ||
| 74 | + </p> | ||
| 75 | + <van-button block | ||
| 76 | + class="!rounded-lg !bg-transparent !border-[#FFDD01] !text-[#FFDD01] !font-bold !text-lg !h-[48px] !max-w-xs" | ||
| 77 | + @click="handleViewHistory"> | ||
| 78 | + 我的星光 | ||
| 79 | + </van-button> | ||
| 80 | + </div> | ||
| 81 | + | ||
| 82 | + </div> | ||
| 83 | + </swiper-slide> | ||
| 84 | + </swiper> | ||
| 85 | + </div> | ||
| 86 | +</template> | ||
| 87 | + | ||
| 88 | +<script setup> | ||
| 89 | +import { ref, computed } from 'vue' | ||
| 90 | +import { useRouter } from 'vue-router' | ||
| 91 | +import { useTitle } from '@vueuse/core' | ||
| 92 | +import dayjs from 'dayjs' | ||
| 93 | +import { Swiper, SwiperSlide } from 'swiper/vue' | ||
| 94 | +import { Mousewheel } from 'swiper/modules' | ||
| 95 | +import 'swiper/css' | ||
| 96 | + | ||
| 97 | +// Assets | ||
| 98 | +import bgImg from '@/assets/images/recall/bg01@2x.png' | ||
| 99 | +import title03 from '@/assets/images/recall/title03@2x.png' | ||
| 100 | +import title04 from '@/assets/images/recall/title04@2x.png' // Assuming title04 is "我的BEHALO足迹" | ||
| 101 | +import arrowDown from '@/assets/images/recall/xia@2x.png' | ||
| 102 | + | ||
| 103 | +// Context (Mocking auth for now as we might be in a detached flow, | ||
| 104 | +// but try to use useAuth if available or fallback to local storage/mock) | ||
| 105 | +import { useAuth } from '@/contexts/auth' | ||
| 106 | + | ||
| 107 | +const router = useRouter() | ||
| 108 | +useTitle('时光机') | ||
| 109 | + | ||
| 110 | +const { currentUser } = useAuth() | ||
| 111 | + | ||
| 112 | +// Swiper logic | ||
| 113 | +const swiperInstance = ref(null) | ||
| 114 | +const onSwiper = (swiper) => { | ||
| 115 | + swiperInstance.value = swiper | ||
| 116 | +} | ||
| 117 | +const slideNext = () => { | ||
| 118 | + swiperInstance.value?.slideNext() | ||
| 119 | +} | ||
| 120 | +const onSlideChange = () => { | ||
| 121 | + // Can add tracking or animation triggers here | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +// Data Logic | ||
| 125 | +const userName = computed(() => currentUser.value?.name || '张开心') // Fallback to design mock name | ||
| 126 | +const joinDate = computed(() => { | ||
| 127 | + return currentUser.value?.created_at || currentUser.value?.reg_time || '2020-09-12' // Fallback to design mock date | ||
| 128 | +}) | ||
| 129 | + | ||
| 130 | +const joinYear = computed(() => dayjs(joinDate.value).year()) | ||
| 131 | + | ||
| 132 | +const joinDateFormatted = computed(() => { | ||
| 133 | + return dayjs(joinDate.value).format('YYYY年M月D日') | ||
| 134 | +}) | ||
| 135 | + | ||
| 136 | +const durationString = computed(() => { | ||
| 137 | + const start = dayjs(joinDate.value) | ||
| 138 | + const now = dayjs() | ||
| 139 | + const years = now.diff(start, 'year') | ||
| 140 | + const months = now.diff(start, 'month') % 12 | ||
| 141 | + | ||
| 142 | + if (years === 0 && months === 0) { | ||
| 143 | + const days = now.diff(start, 'day'); | ||
| 144 | + return `${days}天`; | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + if (years > 0) { | ||
| 148 | + return `${years}年${months}个月` | ||
| 149 | + } else { | ||
| 150 | + return `${months}个月` | ||
| 151 | + } | ||
| 152 | +}) | ||
| 153 | + | ||
| 154 | +// Mock Stats | ||
| 155 | +const activityCount = ref(20) | ||
| 156 | +const volunteerCount = ref(12) | ||
| 157 | + | ||
| 158 | +const handleViewHistory = () => { | ||
| 159 | + // Redirect to activities page or profile activities | ||
| 160 | + router.push('/profile/activities') | ||
| 161 | +} | ||
| 162 | + | ||
| 163 | +</script> | ||
| 164 | + | ||
| 165 | +<style lang="less" scoped> | ||
| 166 | +.timeline-page { | ||
| 167 | + // Custom animations if needed, or use Tailwind utility classes | ||
| 168 | +} | ||
| 169 | + | ||
| 170 | +// Simple fade/zoom animations | ||
| 171 | +@keyframes fadeInDown { | ||
| 172 | + from { | ||
| 173 | + opacity: 0; | ||
| 174 | + transform: translateY(-20px); | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + to { | ||
| 178 | + opacity: 1; | ||
| 179 | + transform: translateY(0); | ||
| 180 | + } | ||
| 181 | +} | ||
| 182 | + | ||
| 183 | +.animate-fade-in-down { | ||
| 184 | + animation: fadeInDown 0.8s ease-out forwards; | ||
| 185 | +} | ||
| 186 | + | ||
| 187 | +@keyframes fadeInUp { | ||
| 188 | + from { | ||
| 189 | + opacity: 0; | ||
| 190 | + transform: translateY(20px); | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + to { | ||
| 194 | + opacity: 1; | ||
| 195 | + transform: translateY(0); | ||
| 196 | + } | ||
| 197 | +} | ||
| 198 | + | ||
| 199 | +.animate-fade-in-up { | ||
| 200 | + animation: fadeInUp 0.8s ease-out forwards; | ||
| 201 | +} | ||
| 202 | + | ||
| 203 | +@keyframes zoomIn { | ||
| 204 | + from { | ||
| 205 | + opacity: 0; | ||
| 206 | + transform: scale(0.95); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + to { | ||
| 210 | + opacity: 1; | ||
| 211 | + transform: scale(1); | ||
| 212 | + } | ||
| 213 | +} | ||
| 214 | + | ||
| 215 | +.animate-zoom-in { | ||
| 216 | + animation: zoomIn 0.8s ease-out forwards; | ||
| 217 | +} | ||
| 218 | + | ||
| 219 | +.delay-100 { | ||
| 220 | + animation-delay: 0.1s; | ||
| 221 | +} | ||
| 222 | + | ||
| 223 | +.delay-200 { | ||
| 224 | + animation-delay: 0.2s; | ||
| 225 | +} | ||
| 226 | +</style> |
-
Please register or login to post a comment