Files
emall-web/src/views/Home.vue
2025-10-16 09:59:34 +08:00

444 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="home-container">
<!-- 深空背景 -->
<div class="space-background">
<div class="stars" ref="stars"></div>
</div>
<!-- 顶部欢迎语 -->
<div class="header glass-card">
<div class="welcome-text">
<h2>欢迎回来{{ userName }}</h2>
<p>{{ greetingText }}</p>
</div>
<div class="header-actions">
<van-icon name="search" size="24" @click="showSearch = true" />
<van-icon name="bell" size="24" @click="showNotifications = true" />
</div>
</div>
<!-- 时光胶囊视图 -->
<div class="capsules-container">
<div class="capsules-space" ref="capsulesSpace">
<!-- 时间胶囊 -->
<div
v-for="capsule in capsules"
:key="capsule.id"
class="capsule-wrapper"
:style="getCapsuleStyle(capsule)"
@click="openCapsule(capsule)"
>
<div class="time-capsule" :class="{'glowing': capsule.isGlowing}">
<div class="capsule-info">
<p class="capsule-title">{{ capsule.title }}</p>
<p class="capsule-date">{{ formatDate(capsule.deliveryDate) }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- 悬浮按钮 -->
<div class="fab-container">
<van-button
icon="plus"
type="primary"
round
class="fab-button"
@click="goToCompose"
>
撰写邮件
</van-button>
</div>
<!-- 底部导航 -->
<van-tabbar v-model="active" class="custom-tabbar">
<van-tabbar-item icon="home-o" to="/home">时光胶囊</van-tabbar-item>
<van-tabbar-item icon="envelop-o" to="/inbox">收件箱</van-tabbar-item>
<van-tabbar-item icon="send-o" to="/sent">发件箱</van-tabbar-item>
<van-tabbar-item icon="user-o" to="/profile">个人中心</van-tabbar-item>
</van-tabbar>
<!-- 搜索弹窗 -->
<van-popup v-model:show="showSearch" position="top" :style="{ height: '30%' }">
<div class="search-popup">
<van-search
v-model="searchValue"
placeholder="搜索邮件"
@search="onSearch"
/>
</div>
</van-popup>
<!-- 通知弹窗 -->
<van-popup v-model:show="showNotifications" position="top" :style="{ height: '40%' }">
<div class="notifications-popup">
<h3>通知</h3>
<div v-if="notifications.length === 0" class="empty-notifications">
<p>暂无新通知</p>
</div>
<div v-else>
<div
v-for="notification in notifications"
:key="notification.id"
class="notification-item"
>
<p>{{ notification.message }}</p>
<span class="notification-time">{{ formatTime(notification.time) }}</span>
</div>
</div>
</div>
</van-popup>
</div>
</template>
<script>
import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { showFailToast } from 'vant'
import { userState, mailState, mailActions } from '../store'
export default {
name: 'Home',
setup() {
const router = useRouter()
const active = ref(0)
const stars = ref(null)
const capsulesSpace = ref(null)
const showSearch = ref(false)
const showNotifications = ref(false)
const searchValue = ref('')
// 使用直接导入的状态和操作
const userName = computed(() => userState.userInfo.username || '时光旅人')
const capsules = computed(() => mailState.sentList || [])
const notifications = ref([]) // 暂时使用空数组,可以后续添加通知功能
// 根据时间获取问候语
const greetingText = computed(() => {
const hour = new Date().getHours()
if (hour < 6) return '夜深了,注意休息'
if (hour < 12) return '早上好,美好的一天开始了'
if (hour < 18) return '下午好,继续加油'
return '晚上好,今天过得怎么样'
})
// 生成星空背景
const generateStars = () => {
if (!stars.value) return
const starsContainer = stars.value
const starCount = 150
for (let i = 0; i < starCount; i++) {
const star = document.createElement('div')
star.className = 'star'
// 随机位置
const left = Math.random() * 100
const top = Math.random() * 100
// 随机大小
const size = Math.random() * 3 + 1
// 随机动画延迟
const delay = Math.random() * 4
star.style.left = `${left}%`
star.style.top = `${top}%`
star.style.width = `${size}px`
star.style.height = `${size}px`
star.style.animationDelay = `${delay}s`
starsContainer.appendChild(star)
}
}
// 获取胶囊样式
const getCapsuleStyle = (capsule) => {
return {
left: `${capsule.position.x}%`,
top: `${capsule.position.y}%`,
transform: `scale(${0.5 + capsule.position.z})`,
opacity: 0.5 + capsule.position.z * 0.5,
zIndex: Math.floor(capsule.position.z * 10)
}
}
// 格式化日期
const formatDate = (date) => {
const now = new Date()
const targetDate = new Date(date)
const diffTime = targetDate - now
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
if (diffDays < 0) return '已送达'
if (diffDays === 0) return '今天'
if (diffDays === 1) return '明天'
if (diffDays < 7) return `${diffDays}天后`
if (diffDays < 30) return `${Math.floor(diffDays / 7)}周后`
if (diffDays < 365) return `${Math.floor(diffDays / 30)}个月后`
return `${Math.floor(diffDays / 365)}年后`
}
// 格式化时间
const formatTime = (time) => {
const now = new Date()
const targetDate = new Date(time)
const diffTime = now - targetDate
const diffMinutes = Math.floor(diffTime / (1000 * 60))
if (diffMinutes < 1) return '刚刚'
if (diffMinutes < 60) return `${diffMinutes}分钟前`
const diffHours = Math.floor(diffMinutes / 60)
if (diffHours < 24) return `${diffHours}小时前`
const diffDays = Math.floor(diffHours / 24)
if (diffDays < 7) return `${diffDays}天前`
return targetDate.toLocaleDateString()
}
// 打开胶囊详情
const openCapsule = (capsule) => {
router.push(`/capsule/${capsule.id}`)
}
// 跳转到撰写页面
const goToCompose = () => {
router.push('/compose')
}
// 搜索处理
const onSearch = (value) => {
if (!value) {
showFailToast('请输入搜索内容')
return
}
router.push(`/search?q=${encodeURIComponent(value)}`)
showSearch.value = false
}
// 获取时光胶囊数据
const fetchCapsules = async () => {
try {
await mailActions.getCapsules()
} catch (error) {
showFailToast('获取时光胶囊数据失败')
}
}
// 获取通知数据
const fetchNotifications = async () => {
try {
await mailActions.getNotifications()
} catch (error) {
console.error('获取通知失败:', error)
}
}
onMounted(async () => {
generateStars()
await fetchCapsules()
await fetchNotifications()
})
return {
active,
userName,
greetingText,
stars,
capsulesSpace,
capsules,
showSearch,
showNotifications,
searchValue,
notifications,
getCapsuleStyle,
formatDate,
formatTime,
openCapsule,
goToCompose,
onSearch
}
}
}
</script>
<style scoped>
.home-container {
height: 100vh;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.welcome-text h2 {
margin: 0;
font-size: 18px;
font-weight: bold;
}
.welcome-text p {
margin: 5px 0 0;
font-size: 14px;
color: var(--text-secondary);
}
.header-actions {
display: flex;
gap: 15px;
}
.capsules-container {
flex: 1;
position: relative;
overflow: hidden;
}
.capsules-space {
position: relative;
width: 100%;
height: 100%;
}
.capsule-wrapper {
position: absolute;
cursor: pointer;
transition: all 0.3s ease;
}
.capsule-wrapper:hover {
transform: scale(1.1);
}
.time-capsule {
width: 60px;
height: 90px;
background: var(--gradient-color);
border-radius: 30px;
position: relative;
box-shadow: 0 8px 20px rgba(0, 212, 255, 0.3);
transition: all 0.3s ease;
animation: float 6s ease-in-out infinite;
}
.time-capsule::before {
content: '';
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 40px;
background: radial-gradient(circle, var(--accent-color), transparent);
border-radius: 50%;
opacity: 0.7;
}
.time-capsule.glowing::before {
animation: pulse 2s infinite alternate;
}
.capsule-info {
position: absolute;
bottom: -40px;
left: 50%;
transform: translateX(-50%);
width: 100px;
text-align: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.capsule-wrapper:hover .capsule-info {
opacity: 1;
}
.capsule-title {
font-size: 12px;
font-weight: bold;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.capsule-date {
font-size: 10px;
color: var(--text-secondary);
margin: 2px 0 0;
}
@keyframes pulse {
0% {
opacity: 0.4;
transform: translateX(-50%) scale(0.8);
}
100% {
opacity: 1;
transform: translateX(-50%) scale(1.2);
}
}
.fab-container {
position: absolute;
bottom: 80px;
right: 20px;
z-index: 10;
}
.fab-button {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #00D4FF, #1D3B5A);
border: none;
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.4);
font-size: 14px;
}
.search-popup {
padding: 20px;
}
.notifications-popup {
padding: 20px;
height: 100%;
overflow-y: auto;
}
.notifications-popup h3 {
margin: 0 0 15px;
font-size: 18px;
}
.empty-notifications {
display: flex;
justify-content: center;
align-items: center;
height: 100px;
}
.notification-item {
padding: 10px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.notification-item:last-child {
border-bottom: none;
}
.notification-item p {
margin: 0 0 5px;
font-size: 14px;
}
.notification-time {
font-size: 12px;
color: var(--text-secondary);
}
</style>