hookehuyr

feat(recall): 添加时光机旅程页面和路由配置

- 新增时光机旅程页面组件,包含欢迎和足迹两个垂直滑动页面
- 添加相关图片资源文件
- 更新路由配置,修改路由名称和页面标题
- 实现用户加入时间计算和活动统计展示功能
...@@ -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',
......
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>