hookehuyr

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

- 新增时光机旅程页面组件,包含欢迎和足迹两个垂直滑动页面
- 添加相关图片资源文件
- 更新路由配置,修改路由名称和页面标题
- 实现用户加入时间计算和活动统计展示功能
......@@ -128,10 +128,11 @@ export const routes = [
},
{
path: '/recall/timeline',
name: 'Timeline',
name: 'RecallTimeline',
component: () => import('../views/recall/timeline.vue'),
meta: { title: '加入时间/活动记录' },
meta: { title: '时光机旅程' },
},
{
path: '/checkout',
name: 'CheckoutPage',
......
<template>
<div class="timeline-page w-full h-screen relative overflow-hidden">
<!-- Shared Background -->
<StarryBackground :bg-image="bgImg" class="absolute inset-0 z-0" />
<!-- Swiper -->
<swiper :direction="'vertical'" :modules="[Mousewheel]" :mousewheel="true" :speed="800"
class="h-full w-full relative z-10" @swiper="onSwiper" @slideChange="onSlideChange">
<!-- Slide 1: Welcome Back -->
<swiper-slide>
<div class="flex flex-col items-center h-full w-full px-6 pt-16 pb-8 relative">
<!-- Title Section -->
<div class="flex flex-col items-center mb-8 animate-fade-in-down">
<h2 class="text-[#FFDD01] text-2xl font-bold mb-4 tracking-wider">@{{ userName }}</h2>
<img :src="title03" class="w-64 object-contain" alt="Welcome Back" />
</div>
<!-- Card Section -->
<FrostedGlass
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"
:bg-opacity="15" blur-level="md">
<p class="text-white text-sm mb-6 tracking-wide opacity-90">您加入BEHALO星球的时间</p>
<h1 class="text-[#FFDD01] text-3xl font-bold mb-4 tracking-wider">{{ joinDateFormatted }}</h1>
<p class="text-white text-lg tracking-wide font-medium">已有{{ durationString }}</p>
</FrostedGlass>
<!-- Bottom Section -->
<div class="mt-auto flex flex-col items-center text-center animate-fade-in-up">
<p class="text-white/80 text-xs mb-2 tracking-wider leading-relaxed">
从{{ joinYear }}年起,每一段旅程都被珍藏。<br>
欢迎回家,继续书写我们共有的星球诗篇。
</p>
<div class="mt-4 cursor-pointer animate-bounce" @click="slideNext">
<img :src="arrowDown" class="w-6 h-6" alt="Scroll Down" />
</div>
</div>
</div>
</swiper-slide>
<!-- Slide 2: Footprints -->
<swiper-slide>
<div class="flex flex-col items-center h-full w-full px-6 pt-16 pb-8 relative">
<!-- Title Section -->
<div class="flex flex-col items-center mb-10 animate-fade-in-down">
<img :src="title04" class="w-64 object-contain mb-4" alt="My Footprints" />
<h2 class="text-[#FFDD01] text-2xl font-bold tracking-wider">@{{ userName }}</h2>
</div>
<!-- Card Section -->
<FrostedGlass
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"
:bg-opacity="15" blur-level="md">
<div class="flex flex-col items-center mb-8">
<p class="text-white text-sm mb-2 tracking-wide opacity-90">参与星球活动</p>
<h1 class="text-[#FFDD01] text-4xl font-bold tracking-wider">{{ activityCount }}次</h1>
</div>
<div class="flex flex-col items-center">
<p class="text-white text-sm mb-2 tracking-wide opacity-90">义工服务记录</p>
<h1 class="text-[#FFDD01] text-4xl font-bold tracking-wider">{{ volunteerCount }}次</h1>
</div>
</FrostedGlass>
<!-- Bottom Section -->
<div class="mt-auto w-full flex flex-col items-center text-center animate-fade-in-up delay-200">
<p class="text-white/80 text-xs mb-6 tracking-wider leading-relaxed">
每一次参与,<br>
都是这片星球上不灭的星光。
</p>
<van-button block
class="!rounded-lg !bg-transparent !border-[#FFDD01] !text-[#FFDD01] !font-bold !text-lg !h-[48px] !max-w-xs"
@click="handleViewHistory">
我的星光
</van-button>
</div>
</div>
</swiper-slide>
</swiper>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useTitle } from '@vueuse/core'
import dayjs from 'dayjs'
import { Swiper, SwiperSlide } from 'swiper/vue'
import { Mousewheel } from 'swiper/modules'
import 'swiper/css'
// Assets
import bgImg from '@/assets/images/recall/bg01@2x.png'
import title03 from '@/assets/images/recall/title03@2x.png'
import title04 from '@/assets/images/recall/title04@2x.png' // Assuming title04 is "我的BEHALO足迹"
import arrowDown from '@/assets/images/recall/xia@2x.png'
// Context (Mocking auth for now as we might be in a detached flow,
// but try to use useAuth if available or fallback to local storage/mock)
import { useAuth } from '@/contexts/auth'
const router = useRouter()
useTitle('时光机')
const { currentUser } = useAuth()
// Swiper logic
const swiperInstance = ref(null)
const onSwiper = (swiper) => {
swiperInstance.value = swiper
}
const slideNext = () => {
swiperInstance.value?.slideNext()
}
const onSlideChange = () => {
// Can add tracking or animation triggers here
}
// Data Logic
const userName = computed(() => currentUser.value?.name || '张开心') // Fallback to design mock name
const joinDate = computed(() => {
return currentUser.value?.created_at || currentUser.value?.reg_time || '2020-09-12' // Fallback to design mock date
})
const joinYear = computed(() => dayjs(joinDate.value).year())
const joinDateFormatted = computed(() => {
return dayjs(joinDate.value).format('YYYY年M月D日')
})
const durationString = computed(() => {
const start = dayjs(joinDate.value)
const now = dayjs()
const years = now.diff(start, 'year')
const months = now.diff(start, 'month') % 12
if (years === 0 && months === 0) {
const days = now.diff(start, 'day');
return `${days}天`;
}
if (years > 0) {
return `${years}年${months}个月`
} else {
return `${months}个月`
}
})
// Mock Stats
const activityCount = ref(20)
const volunteerCount = ref(12)
const handleViewHistory = () => {
// Redirect to activities page or profile activities
router.push('/profile/activities')
}
</script>
<style lang="less" scoped>
.timeline-page {
// Custom animations if needed, or use Tailwind utility classes
}
// Simple fade/zoom animations
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-down {
animation: fadeInDown 0.8s ease-out forwards;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fadeInUp 0.8s ease-out forwards;
}
@keyframes zoomIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.animate-zoom-in {
animation: zoomIn 0.8s ease-out forwards;
}
.delay-100 {
animation-delay: 0.1s;
}
.delay-200 {
animation-delay: 0.2s;
}
</style>