<template>
<div class="calendar">
<!-- 日历头 -->
<div class="header">
<button @click="prevMonth">←</button>
<h2>{{ currentMonth }}</h2>
<!-- <button @click="nextMonth">→</button> -->
</div>
<div class="weekdays">
<div v-for="(day, index) in weekdays" :key="index" class="weekday">
{{ day }}
</div>
</div>
<!-- 日历网格 -->
<div class="days-grid">
<div
v-for="(day, index) in visibleDays"
:key="index"
class="day"
:class="{ 'not-current-month': !day.isCurrentMonth }"
>
<div class="date">{{ day.date.getDate() }}</div>
<div v-for="event in getEventsForDate(day.date)" :key="event.id" class="event">
{{ event.title }}
</div>
<button @click="openAddEvent(day.date)">+ 添加事件</button>
</div>
</div>
<!-- 添加事件弹窗 -->
<div v-if="showAddEvent" class="modal">
<input v-model="newEvent.title" placeholder="事件标题" />
<button @click="addEvent">保存</button>
<button @click="cancelAddEvent">取消</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
const weekdays = ["日", "一", "二", "三", "四", "五", "六"];
interface CalendarEvent {
id: string;
title: string;
date: Date;
}
const currentDate = ref(new Date());
const events = ref<CalendarEvent[]>([]);
const showAddEvent = ref(false);
const newEvent = ref({
title: "",
date: new Date(),
});
// 生成可见日期(包含前后月的补白)
const visibleDays = computed(() => {
const year = currentDate.value.getFullYear();
const month = currentDate.value.getMonth();
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
// 生成当前月所有日期
const days = Array.from({ length: lastDay.getDate() }, (_, i) => ({
date: new Date(year, month, i + 1),
isCurrentMonth: true,
}));
// 补充前月日期
const firstWeekday = firstDay.getDay();
for (let i = 0; i < firstWeekday; i++) {
days.unshift({
date: new Date(year, month, -i),
isCurrentMonth: false,
});
}
// 补充后月日期
const totalCells = Math.ceil(days.length / 7) * 7;
while (days.length < totalCells) {
days.push({
date: new Date(year, month + 1, days.length - lastDay.getDate() + 1),
isCurrentMonth: false,
});
}
return days;
});
// 获取当前月名称
const currentMonth = computed(() => {
return currentDate.value.toLocaleString("default", {
month: "long",
year: "numeric",
});
});
// 存储到localStorage
const saveEvents = () => {
localStorage.setItem("calendarEvents", JSON.stringify(events.value));
};
// 加载事件
const loadEvents = () => {
const stored = localStorage.getItem("calendarEvents");
if (stored) {
events.value = JSON.parse(stored).map((e: any) => ({
...e,
date: new Date(e.date),
}));
}
};
// 打开添加事件弹窗
const openAddEvent = (date: Date) => {
newEvent.value.date = date;
showAddEvent.value = true;
};
// 添加事件
const addEvent = () => {
if (newEvent.value.title.trim()) {
events.value.push({
id: Date.now().toString(),
...newEvent.value,
});
saveEvents();
cancelAddEvent();
}
};
// 取消添加
const cancelAddEvent = () => {
showAddEvent.value = false;
newEvent.value.title = "";
};
// 获取某天的事件
const getEventsForDate = (date: Date) => {
return events.value.filter(
(event) => event.date.toDateString() === date.toDateString()
);
};
// 切换月份
const prevMonth = () => {
currentDate.value = new Date(
currentDate.value.getFullYear(),
currentDate.value.getMonth() - 1
);
};
const nextMonth = () => {
currentDate.value = new Date(
currentDate.value.getFullYear(),
currentDate.value.getMonth() + 1
);
};
// 初始化加载
onMounted(loadEvents);
</script>
<style>
.days-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 5px;
}
.weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 5px;
margin: 10px 0;
font-weight: bold;
text-align: center;
}
.day {
min-height: 80px;
padding: 5px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.not-current-month {
background-color: #f0f0f0;
}
.event {
background-color: #e3f2fd;
margin: 2px;
padding: 2px;
border-radius: 3px;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border: 1px solid #ccc;
}
</style>
这个里面有事件的逻辑,把里面事件的逻辑抽出来,放到这个<template>
<div class="calendar">
<div class="calendar-header">
<!-- <button style="background-color: transparent; border:0" @click="prevMonth"><</button> -->
<div class="shou" @click="prevMonth">
<LeftOutlined />
</div>
<span>{{ currentYear }}年{{ currentMonth + 1 }}月</span>
<!-- <button @click="nextMonth">></button> -->
<div class="shou" @click="nextMonth">
<RightOutlined />
</div>
</div>
<div class="weekdays">
<div v-for="(day, index) in weekdays" :key="index" class="weekday">
{{ day }}
</div>
</div>
<div class="calendar-grid">
<div class="calendar-row" v-for="(week, index) in weeks" :key="index">
<div
v-for="day in week"
:key="day.date"
class="calendar-day"
:class="{
'not-current-month': !day.isCurrentMonth,
activeDay:
(day.isToday && !homeData.home.selectedDay) ||
(homeData.home.selectedDay &&
day.date.toDateString() === homeData.home.selectedDay),
}"
@click="handleDayClick(day)"
>
{{ day.day }}
<div class="dian"></div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
import { LeftOutlined, RightOutlined } from "@ant-design/icons-vue";
import homeStore from "@/store/home/index";
const homeData = homeStore();
const weekdays = ["日", "一", "二", "三", "四", "五", "六"];
const currentDate = ref(new Date());
// 生成日历数据
const generateCalendar = (date) => {
const year = date.getFullYear();
const month = date.getMonth();
const today = new Date();
// 当月第一天
const firstDay = new Date(year, month, 1);
// 上个月最后一天
const lastDayOfPrevMonth = new Date(year, month, 0);
// 当月最后一天
const lastDay = new Date(year, month + 1, 0);
// 计算起始偏移(周几)
const startOffset = firstDay.getDay() === 0 ? 6 : firstDay.getDay() - 1;
// 确保始终生成5行(35天)
const totalDays = 35;
const days = [];
// 添加上月日期
for (let i = startOffset - 1; i >= 0; i--) {
const day = lastDayOfPrevMonth.getDate() - i;
days.push({
day,
date: new Date(year, month - 1, day),
isCurrentMonth: false,
isToday: false,
});
}
// 添加当月日期
for (let i = 1; i <= lastDay.getDate(); i++) {
const date = new Date(year, month, i);
days.push({
day: i,
date,
isCurrentMonth: true,
isToday: date.toDateString() === today.toDateString(),
});
}
// 补充下月日期
let nextMonthDay = 1;
while (days.length < totalDays) {
days.push({
day: nextMonthDay,
date: new Date(year, month + 1, nextMonthDay),
isCurrentMonth: false,
isToday: false,
});
nextMonthDay++;
}
// 分组为5行(每周7天)
const weeks = [];
for (let i = 0; i < 5; i++) {
weeks.push(days.slice(i * 7, (i + 1) * 7));
}
return weeks;
};
// 计算属性
const weeks = computed(() => generateCalendar(currentDate.value));
const currentYear = computed(() => currentDate.value.getFullYear());
const currentMonth = computed(() => currentDate.value.getMonth());
// 月份切换
const prevMonth = () => {
currentDate.value = new Date(
currentDate.value.getFullYear(),
currentDate.value.getMonth() - 1
);
};
const nextMonth = () => {
currentDate.value = new Date(
currentDate.value.getFullYear(),
currentDate.value.getMonth() + 1
);
};
// 日期点击处理
const handleDayClick = (day) => {
if (day.isCurrentMonth) {
homeData.home.selectedDay = day.date.toDateString(); // 存储为标准日期字符串
homeData.home.forecastShow = true;
console.log("选中日期:", day.date);
}
};
</script>
<style scoped>
.calendar {
width: 100%;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
font-size: 18px;
}
.calendar-grid {
display: grid;
grid-template-rows: repeat(5, 1fr);
gap: 4px;
}
.weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 5px;
margin: 10px 0;
font-weight: bold;
text-align: center;
}
.calendar-row {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
}
.calendar-day {
padding: 5px;
min-height: 64px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 4px;
position: relative;
/* transition: background 0.2s; */
}
.dian {
position: absolute;
background-color: transparent;
padding: 4px;
border-radius: 50%;
top: 68%;
}
.calendar-day:hover {
border-radius: 50%;
background: #f0f0f0;
}
.not-current-month {
color: #9a9a9a;
pointer-events: none;
}
.activeDay {
background: #eb7162 !important;
color: white;
border-radius: 50%;
font-size: 18px;
}
</style>
代码里面,要写完整代码
最新发布