Files
home/docs/.vitepress/theme/components/Calendar.vue
2026-05-13 16:24:00 +08:00

241 lines
5.3 KiB
Vue
Raw Permalink 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 { ref, onMounted, onUnmounted, computed } from 'vue'
const currentTime = ref(new Date())
const selectedDate = ref(new Date())
let timer: ReturnType<typeof setInterval>
onMounted(() => {
timer = setInterval(() => {
currentTime.value = new Date()
}, 1000)
})
onUnmounted(() => {
clearInterval(timer)
})
const timeString = computed(() => {
const hours = currentTime.value.getHours().toString().padStart(2, '0')
const minutes = currentTime.value.getMinutes().toString().padStart(2, '0')
const seconds = currentTime.value.getSeconds().toString().padStart(2, '0')
return `${hours}:${minutes}:${seconds}`
})
const dateString = computed(() => {
const year = currentTime.value.getFullYear()
const month = (currentTime.value.getMonth() + 1).toString().padStart(2, '0')
const day = currentTime.value.getDate().toString().padStart(2, '0')
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
const weekDay = weekDays[currentTime.value.getDay()]
return `${year}${month}${day}${weekDay}`
})
const currentYear = computed(() => selectedDate.value.getFullYear())
const currentMonth = computed(() => selectedDate.value.getMonth())
const monthNames = [
'一月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '十一月', '十二月'
]
const weekDayNames = ['日', '一', '二', '三', '四', '五', '六']
const daysInMonth = computed(() => {
return new Date(currentYear.value, currentMonth.value + 1, 0).getDate()
})
const firstDayOfMonth = computed(() => {
return new Date(currentYear.value, currentMonth.value, 1).getDay()
})
const calendarDays = computed(() => {
const days = []
for (let i = 0; i < firstDayOfMonth.value; i++) {
days.push(null)
}
for (let i = 1; i <= daysInMonth.value; i++) {
days.push(i)
}
return days
})
function prevMonth() {
selectedDate.value = new Date(currentYear.value, currentMonth.value - 1, 1)
}
function nextMonth() {
selectedDate.value = new Date(currentYear.value, currentMonth.value + 1, 1)
}
function isToday(day: number | null) {
if (!day) return false
return (
day === currentTime.value.getDate() &&
currentMonth.value === currentTime.value.getMonth() &&
currentYear.value === currentTime.value.getFullYear()
)
}
</script>
<template>
<div class="calendar">
<div class="calendar-header">
<div class="time">{{ timeString }}</div>
<div class="date">{{ dateString }}</div>
</div>
<div class="calendar-body">
<div class="month-nav">
<button class="nav-btn" @click="prevMonth"></button>
<span class="month-title">{{ monthNames[currentMonth] }} {{ currentYear }}</span>
<button class="nav-btn" @click="nextMonth"></button>
</div>
<div class="weekdays">
<div v-for="day in weekDayNames" :key="day" class="weekday">{{ day }}</div>
</div>
<div class="calendar-grid">
<div
v-for="(day, index) in calendarDays"
:key="index"
class="calendar-day"
:class="{ 'calendar-day-today': isToday(day), 'calendar-day-empty': !day }"
>
{{ day }}
</div>
</div>
</div>
</div>
</template>
<style scoped>
.calendar {
color: white;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.calendar-header {
text-align: center;
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 10px;
flex-shrink: 0;
}
.time {
font-size: 28px;
font-weight: 200;
letter-spacing: 2px;
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
margin-bottom: 2px;
}
.date {
font-size: 12px;
opacity: 0.8;
font-weight: 400;
}
.calendar-body {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.month-nav {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
flex-shrink: 0;
}
.nav-btn {
background: rgba(255, 255, 255, 0.1);
border: none;
color: white;
width: 24px;
height: 24px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
transition: all 0.2s ease;
}
.nav-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.month-title {
font-size: 13px;
font-weight: 600;
}
.weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
margin-bottom: 4px;
flex-shrink: 0;
}
.weekday {
text-align: center;
font-size: 11px;
font-weight: 600;
opacity: 0.6;
padding: 2px 0;
}
.calendar-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
min-height: 0;
}
.calendar-day {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
border-radius: 4px;
transition: all 0.2s ease;
cursor: pointer;
background: rgba(255, 255, 255, 0.05);
min-height: 0;
flex: 1;
}
.calendar-day:hover:not(.calendar-day-empty) {
background: rgba(255, 255, 255, 0.15);
}
.calendar-day-today {
background: rgba(0, 122, 255, 0.4);
font-weight: 600;
color: white;
}
.calendar-day-empty {
background: transparent;
cursor: default;
}
</style>