Files
SuBlog/pages/index.vue
T
2026-06-29 23:46:34 +08:00

220 lines
7.5 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.
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { siteConfig } from '~/config/site'
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
// 下一个周六或周日
const dayOfWeek = today.getDay()
const daysToSat = dayOfWeek === 6 ? 0 : (6 - dayOfWeek + 7) % 7
const nextWeekend = new Date(today)
nextWeekend.setDate(today.getDate() + daysToSat)
const weekendDays = daysUntil(nextWeekend)
// 从 API 获取法定节假日
const { data: holidaysData } = await useAsyncData('holidays', () =>
$fetch('https://timor.tech/api/holiday/year/' + today.getFullYear()).catch(() => null)
)
const nextHoliday = computed(() => {
if (!holidaysData.value) return null
const list = holidaysData.value?.holiday
if (!list) return null
const entries = Object.values(list) as any[]
const upcoming = entries
.filter((h: any) => h.date && new Date(h.date + 'T00:00:00') >= today)
.sort((a: any, b: any) => a.date.localeCompare(b.date))
return upcoming[0] || null
})
const holidayDays = computed(() => {
if (!nextHoliday.value) return null
return daysUntil(new Date(nextHoliday.value.date + 'T00:00:00'))
})
function daysUntil(d: Date): number {
return Math.ceil((d.getTime() - today.getTime()) / 86400000)
}
useHead({
script: [
{ src: '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js', async: true, defer: true },
],
})
// 网易云热评
const { data: hitokoto } = await useAsyncData('wyy-comment', () => $fetch<{ user: string; music: string; content: string }>('/api/wyy'))
onMounted(() => {
const el = document.getElementById('busuanzi_value_site_pv')
if (!el) return
el.textContent = '1'
const animate = (target: number) => {
if (target <= 1) return
const duration = 800
const start = performance.now()
const tick = (now: number) => {
const progress = Math.min((now - start) / duration, 1)
const eased = 1 - Math.pow(1 - progress, 3)
el.textContent = Math.floor(1 + (target - 1) * eased).toLocaleString()
if (progress < 1) requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
}
const observer = new MutationObserver(() => {
const val = parseInt(el.textContent || '0', 10)
if (val > 1) {
observer.disconnect()
animate(val)
}
})
observer.observe(el, { childList: true, characterData: true, subtree: true })
setTimeout(() => observer.disconnect(), 10000)
})
</script>
<template>
<div class="flex flex-col items-center gap-8 px-4 pt-4 pb-12">
<!-- 公告卡片 -->
<div class="w-full max-w-2xl text-center anim-fade-in-up">
<div class="announcement-happy rounded-xl p-6">
<p class="announcement-text text-sm md:text-base">
<strong>欢迎来到{{ siteConfig.siteName }}~ 用代码创造快乐用热爱点亮生活 (゚ヮ゚)</strong>
</p>
<p class="announcement-text text-sm md:text-base mt-1">
遇到问题的话可以去 <a href="https://github.com/wishesl/SuBlog/issues" target="_blank" rel="noopener noreferrer">Issues</a> 提反馈哦会尽快修的
</p>
</div>
</div>
<!-- 头像 -->
<div class="anim-fade-in-up anim-delay-1">
<img
:src="siteConfig.avatar"
:alt="siteConfig.siteName"
class="h-32 w-32 rounded-full shadow-lg"
/>
</div>
<!-- 标题 + 口号 + 访客 -->
<div class="text-center anim-fade-in-up anim-delay-2">
<h1 class="text-4xl font-bold mb-2">{{ siteConfig.siteName }}</h1>
<p v-if="hitokoto" class="text-sm italic leading-relaxed mb-1" style="color: var(--color-text-secondary)">
"{{ hitokoto.content }}"
</p>
<p v-if="hitokoto" class="text-xs opacity-60 mb-1" style="color: var(--color-text-secondary)">
{{ hitokoto.user }} · {{ hitokoto.music }}
</p>
<p class="text-sm mt-6" style="color: var(--color-text-secondary)">
您是第 <span id="busuanzi_value_site_pv" class="font-medium" style="color: var(--color-primary)"></span> 个访客
</p>
</div>
<!-- 放假倒计时 -->
<div class="w-full max-w-sm anim-fade-in-up anim-delay-3">
<div class="card relative overflow-hidden py-4">
<div class="flex items-center justify-center gap-6 text-sm">
<div class="flex items-center gap-2">
<img src="https://api.iconify.design/mdi:calendar-weekend.svg?color=%239ca3af" alt="" class="w-4 h-4" />
<span class="text-muted-foreground">周末</span>
<span class="font-semibold" style="color: var(--color-primary);">{{ weekendDays }} </span>
</div>
<template v-if="nextHoliday">
<span class="text-muted-foreground/30">|</span>
<div class="flex items-center gap-2">
<img src="https://api.iconify.design/mdi:pine-tree.svg?color=%239ca3af" alt="" class="w-4 h-4" />
<span class="text-muted-foreground">{{ nextHoliday.name }}</span>
<span class="font-semibold" style="color: var(--color-primary);">{{ holidayDays }} </span>
</div>
</template>
</div>
</div>
</div>
<!-- 社交卡片 -->
<div class="w-full max-w-2xl mx-auto anim-fade-in-up anim-delay-4">
<div class="card relative overflow-hidden py-6">
<div class="absolute inset-0 flex items-center justify-center pointer-events-none select-none z-0">
<span class="text-5xl font-black tracking-widest" style="color: var(--color-text); opacity: 0.04;">社交</span>
</div>
<div class="relative z-10 px-6 flex flex-wrap gap-3 justify-center">
<a
v-for="link in siteConfig.socialLinks"
:key="link.name"
:href="link.url"
target="_blank"
rel="noopener noreferrer"
class="btn-pill"
>
<img
:src="`https://api.iconify.design/${link.icon}.svg?color=${encodeURIComponent(link.color || 'currentColor')}`"
:alt="link.name"
class="w-5 h-5"
/>
<span>{{ link.name }}</span>
</a>
</div>
</div>
</div>
<!-- 导航卡片 -->
<div class="w-full max-w-2xl mx-auto anim-fade-in-up anim-delay-5">
<div class="card relative overflow-hidden py-6">
<div class="absolute inset-0 flex items-center justify-center pointer-events-none select-none z-0">
<span class="text-5xl font-black tracking-widest" style="color: var(--color-text); opacity: 0.04;">导航</span>
</div>
<div class="relative z-10 px-6 flex flex-wrap gap-3 justify-center">
<NuxtLink
v-for="link in siteConfig.navLinks"
:key="link.href"
:to="link.href"
class="btn-pill"
>
<Icon :icon="link.icon" class="w-5 h-5" />
<span>{{ link.label }}</span>
</NuxtLink>
</div>
</div>
</div>
</div>
</template>
<style scoped>
@keyframes rainbow-flow {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
.announcement-happy {
animation: rainbow-flow 3s linear infinite;
}
.announcement-text,
.announcement-text * {
background: linear-gradient(90deg,
oklch(0.78 0.18 0),
oklch(0.78 0.18 45),
oklch(0.78 0.18 90),
oklch(0.78 0.18 135),
oklch(0.78 0.18 180),
oklch(0.78 0.18 225),
oklch(0.78 0.18 270),
oklch(0.78 0.18 315),
oklch(0.78 0.18 360)
);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: rainbow-flow 3s linear infinite;
}
</style>