Hast du jemals einen Kalender auf einer Webseite gesehen und gedacht: "Wie zum Teufel haben sie das gemacht?". Für so etwas wäre es vielleicht natürlich, nach einem Plugin zu greifen oder sogar einen eingebetteten Google-Kalender zu verwenden, aber es ist tatsächlich viel einfacher, als du vielleicht denkst. Besonders wenn wir die komponentengesteuerte Kraft von Vue nutzen.
Ich habe eine Demo bei CodeSandbox eingerichtet, damit du sehen kannst, was wir anstreben, aber es ist immer eine gute Idee, klarzustellen, was wir tun wollen.
- Erstelle ein Monatsraster, das die Tage des aktuellen Monats anzeigt
- Zeige Tage aus dem vorherigen und nächsten Monat an, damit das Raster immer voll ist
- Zeige das aktuelle Datum an
- Zeige den Namen des aktuell ausgewählten Monats an
- Navigiere zum vorherigen und nächsten Monat
- Ermögliche dem Benutzer, mit einem einzigen Klick zum aktuellen Monat zurückzukehren
Und wir werden dies als Single-Page-Anwendung erstellen, die Kalenderdaten von Day.js abruft, einer superleichten Utility-Bibliothek.
Schritt 1: Beginne mit dem grundlegenden Markup
Wir werden direkt mit den Vorlagen beginnen. Wenn du neu bei Vue bist, ist Sarahs Einführungsserie ein guter Ausgangspunkt. Es ist auch erwähnenswert, dass ich im gesamten Beitrag auf die Vue 2 Docs verlinken werde. Vue 3 befindet sich derzeit im Beta-Stadium und die Dokumentation dafür kann sich noch ändern.
Beginnen wir mit der Erstellung einer einfachen Vorlage für unseren Kalender. Wir können unser Markup in drei Ebenen gliedern, wobei wir haben:
- Einen Bereich für den Kalenderkopf. Dieser zeigt Komponenten mit dem aktuell ausgewählten Monat und den Elementen, die für die Paginierung zwischen den Monaten zuständig sind.
- Einen Bereich für den Kalenderrasterkopf. Eine Tabellenüberschrift, die eine Liste mit den Wochentagen enthält, beginnend mit Montag.
- Das Kalenderraster. Du weißt schon, jeder Tag des aktuellen Monats, dargestellt als Quadrat im Raster.
Schreiben wir das in eine Datei namens CalendarMonth.vue. Dies wird unsere Hauptkomponente sein.
<!-- CalendarMonth.vue -->
<template>
<!-- Parent container for the calendar month -->
<div class="calendar-month">
<!-- The calendar header -->
<div class="calendar-month-header"
<!-- Month name -->
<CalendarDateIndicator />
<!-- Pagination -->
<CalendarDateSelector />
</div>
<!-- Calendar grid header -->
<CalendarWeekdays />
<!-- Calendar grid -->
<ol class="days-grid">
<CalendarMonthDayItem />
</ol>
</div>
</template>
Jetzt, wo wir etwas Markup zum Arbeiten haben, gehen wir einen Schritt weiter und erstellen die erforderlichen Komponenten.
Schritt 2: Kopfzeilenkomponenten
In unserer Kopfzeile haben wir zwei Komponenten:
CalendarDateIndicatorzeigt den aktuell ausgewählten Monat an.CalendarDateSelectorist für die Paginierung zwischen den Monaten zuständig.
Beginnen wir mit CalendarDateIndicator. Diese Komponente akzeptiert eine selectedDate-Eigenschaft, die ein Day.js-Objekt ist, welches das aktuelle Datum korrekt formatiert und dem Benutzer anzeigt.
<!-- CalendarDateIndicator.vue -->
<template>
<div class="calendar-date-indicator">{{ selectedMonth }}</div>
</template>
<script>
export default {
props: {
selectedDate: {
type: Object,
required: true
}
},
computed: {
selectedMonth() {
return this.selectedDate.format("MMMM YYYY");
}
}
};
</script>
Das war einfach. Gehen wir zur Erstellung der Paginierungskomponente, mit der wir zwischen den Monaten navigieren können. Sie wird drei Elemente enthalten, die für die Auswahl des vorherigen, aktuellen und nächsten Monats zuständig sind. Wir fügen eine Ereignisüberwachung hinzu, die die entsprechende Methode auslöst, wenn das Element geklickt wird.
<!-- CalendarDateSelector.vue -->
<template>
<div class="calendar-date-selector">
<span @click="selectPrevious">﹤</span>
<span @click="selectCurrent">Today</span>
<span @click="selectNext">﹥</span>
</div>
</template>
Dann richten wir im Skriptteil zwei Props ein, die die Komponente akzeptieren wird:
currentDateermöglicht uns, zum aktuellen Monat zurückzukehren, wenn der "Heute"-Button geklickt wird.selectedDategibt an, welcher Monat aktuell ausgewählt ist.
Wir werden auch Methoden definieren, die für die Berechnung des neuen ausgewählten Datums basierend auf dem aktuell ausgewählten Datum zuständig sind, unter Verwendung der Methoden subtract und add von Day.js. Jede Methode wird auch ein Ereignis $emit an die Elternkomponente mit dem neu ausgewählten Monat senden. Dies ermöglicht es uns, den Wert des ausgewählten Datums an einem Ort zu halten – nämlich in unserer CalendarMonth.vue-Komponente – und ihn an alle Kindkomponenten (d. h. Kopfzeile, Kalenderraster) weiterzugeben.
// CalendarDateSelector.vue
<script>
import dayjs from "dayjs";
export default {
name: "CalendarDateSelector",
props: {
currentDate: {
type: String,
required: true
},
selectedDate: {
type: Object,
required: true
}
},
methods: {
selectPrevious() {
let newSelectedDate = dayjs(this.selectedDate).subtract(1, "month");
this.$emit("dateSelected", newSelectedDate);
},
selectCurrent() {
let newSelectedDate = dayjs(this.currentDate);
this.$emit("dateSelected", newSelectedDate);
},
selectNext() {
let newSelectedDate = dayjs(this.selectedDate).add(1, "month");
this.$emit("dateSelected", newSelectedDate);
}
}
};
</script>
Nun kehren wir zur CalendarMonth.vue-Komponente zurück und verwenden unsere neu erstellten Komponenten.
Um sie zu verwenden, müssen wir sie zuerst importieren und registrieren. Außerdem müssen wir die Werte erstellen, die als Props an diese Komponenten übergeben werden.
todayformatiert das heutige Datum korrekt und wird als Wert für den "Heute"-Paginierungsbutton verwendet.selectedDateist das aktuell ausgewählte Datum (standardmäßig auf das heutige Datum gesetzt).
Das Letzte, was wir tun müssen, bevor wir die Komponenten rendern können, ist die Erstellung einer Methode, die für die Änderung des Wertes von selectedDate zuständig ist. Diese Methode wird ausgelöst, wenn das Ereignis von der Paginierungskomponente empfangen wird.
// CalendarMonth.vue
<script>
import dayjs from "dayjs";
import CalendarDateIndicator from "./CalendarDateIndicator";
import CalendarDateSelector from "./CalendarDateSelector";
export default {
components: {
CalendarDateIndicator,
CalendarDateSelector
},
data() {
return {
selectedDate: dayjs(),
today: dayjs().format("YYYY-MM-DD")
};
},
methods: {
selectDate(newSelectedDate) {
this.selectedDate = newSelectedDate;
}
}
};
</script>
Jetzt haben wir alles, was wir brauchen, um unsere Kalenderkopfzeile zu rendern.
<!-- CalendarMonth.vue -->
<template>
<div class="calendar-month">
<div class="calendar-month-header">
<CalendarDateIndicator
:selected-date="selectedDate"
class="calendar-month-header-selected-month"
/>
<CalendarDateSelector
:current-date="today"
:selected-date="selectedDate"
@dateSelected="selectDate"
/>
</div>
</div>
</template>
Das ist ein guter Punkt, um anzuhalten und zu sehen, was wir bisher haben. Unsere Kalenderkopfzeile tut alles, was wir wollen, also machen wir weiter und erstellen Komponenten für unser Kalenderraster.
Schritt 3: Kalenderraster-Komponenten
Hier haben wir wieder zwei Komponenten:
CalendarWeekdayszeigt die Namen der Wochentage an.CalendarMonthDayItemrepräsentiert einen einzelnen Tag im Kalender.
Die Komponente CalendarWeekdays enthält eine Liste, die durch die Wochentagsbeschriftungen iteriert (unter Verwendung der v-for-Direktive) und diese Beschriftung für jeden Wochentag rendert. Im Skriptteil müssen wir unsere Wochentage definieren und eine computed-Eigenschaft erstellen, um sie in der Vorlage verfügbar zu machen und das Ergebnis zu cachen, um eine erneute Berechnung in Zukunft zu vermeiden.
// CalendarWeekdays.vue
<template>
<ol class="day-of-week">
<li
v-for="weekday in weekdays"
:key="weekday"
>
{{ weekday }}
</li>
</ol>
</template>
<script>
const WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
export default {
name: 'CalendarWeekdays',
computed: {
weekdays() {
return WEEKDAYS
}
}
}
</script>
Als nächstes kommt CalendarMonthDayItem. Es ist ein Listenelement, das eine day-Eigenschaft (ein Objekt) und eine boolesche Prop, isToday, erhält, die es uns ermöglicht, das Listenelement zu gestalten, um anzuzeigen, dass es sich um das aktuelle Datum handelt. Wir haben auch eine computed-Eigenschaft, die das erhaltene Tagesobjekt in unser gewünschtes Datumsformat (D, also den numerischen Tag des Monats) formatiert.
// CalendarMonthDayItem.vue
<template>
<li
class="calendar-day"
:class="{
'calendar-day--not-current': !isCurrentMonth,
'calendar-day--today': isToday
}"
>
<span>{{ label }}</span>
</li>
</template>
<script>
import dayjs from "dayjs";
export default {
name: "CalendarMonthDayItem",
props: {
day: {
type: Object,
required: true
},
isCurrentMonth: {
type: Boolean,
default: false
},
isToday: {
type: Boolean,
default: false
}
},
computed: {
label() {
return dayjs(this.day.date).format("D");
}
}
};
</script>
Okay, jetzt, da wir diese beiden Komponenten haben, sehen wir uns an, wie wir sie zu unserer CalendarMonth-Komponente hinzufügen können.
Wir müssen sie zuerst importieren und registrieren. Wir müssen auch eine computed-Eigenschaft erstellen, die ein Array von Objekten zurückgibt, die unsere Tage repräsentieren. Jeder Tag enthält eine date-Eigenschaft und eine isCurrentMonth-Eigenschaft.
// CalendarMonth.vue
<script>
import dayjs from "dayjs";
import CalendarMonthDayItem from "./CalendarMonthDayItem";
import CalendarWeekdays from "./CalendarWeekdays";
export default {
name: "CalendarMonth",
components: {
// ...
CalendarMonthDayItem,
CalendarWeekdays
},
computed: {
days() {
return [
{ date: "2020-06-29", isCurrentMonth: false },
{ date: "2020-06-30", isCurrentMonth: false },
{ date: "2020-07-01", isCurrentMonth: true },
{ date: "2020-07-02", isCurrentMonth: true },
// ...
{ date: "2020-07-31", isCurrentMonth: true },
{ date: "2020-08-01", isCurrentMonth: false },
{ date: "2020-08-02", isCurrentMonth: false }
];
}
}
};
</script>
Dann können wir im Template unsere Komponenten rendern. Wieder verwenden wir die v-for-Direktive, um die erforderliche Anzahl von Tag-Elementen zu rendern.
<!-- CalendarMonth.vue -->
<template>
<div class="calendar-month">
<div class="calendar-month-header">
// ...
</div>
<CalendarWeekdays/>
<ol class="days-grid">
<CalendarMonthDayItem
v-for="day in days"
:key="day.date"
:day="day"
:is-today="day.date === today"
/>
</ol>
</div>
</template>
OK, die Dinge fangen gut an. Schau es dir an, wo wir stehen. Es sieht gut aus, aber wie du wahrscheinlich bemerkt hast, enthält die Vorlage im Moment nur statische Daten. Der Monat ist hartkodiert auf Juli und die Tageszahlen sind ebenfalls hartkodiert. Das werden wir ändern, indem wir berechnen, welches Datum für einen bestimmten Monat angezeigt werden sollte. Tauchen wir in den Code ein!
Schritt 4: Einrichten des aktuellen Monatskalenders
Denken wir darüber nach, wie wir das Datum berechnen können, das für einen bestimmten Monat angezeigt werden soll. Hier kommt Day.js wirklich ins Spiel. Es liefert alle Daten, die wir brauchen, um Daten mit korrekten Kalenderdaten für die richtigen Wochentage eines gegebenen Monats zu platzieren. Es ermöglicht uns, alles abzurufen und festzulegen, vom ersten Tag eines Monats bis zu allen Datumsformatierungsoptionen, die wir zur Anzeige der Daten benötigen.
Wir werden:
- Den aktuellen Monat abrufen
- Berechnen, wo die Tage platziert werden sollen (Wochentage)
- Tage für die Anzeige von Daten aus dem vorherigen und nächsten Monat berechnen
- Alle Tage in einem einzigen Array zusammenfügen
Wir haben Day.js bereits in unserer CalendarMonth-Komponente importiert. Wir werden uns auch auf ein paar Day.js-Plugins verlassen. WeekDay hilft uns, den ersten Tag der Woche festzulegen. Manche bevorzugen Sonntag als ersten Tag der Woche. Andere bevorzugen Montag. Sogar Freitag kann in einigen Fällen als sinnvoller Startpunkt dienen. Wir werden mit Montag beginnen.
Das Plugin WeekOfYear gibt den numerischen Wert der aktuellen Woche aus allen Wochen des Jahres zurück. Es gibt 52 Wochen im Jahr, also würden wir sagen, dass die Woche, die am 1. Januar beginnt, die erste Woche des Jahres ist und so weiter.
Hier ist, was wir in CalendarMonth.vue einfügen, um all das zu nutzen:
// CalendarMonth.vue
<script>
import dayjs from "dayjs";
import weekday from "dayjs/plugin/weekday";
import weekOfYear from "dayjs/plugin/weekOfYear";
// ...
dayjs.extend(weekday);
dayjs.extend(weekOfYear);
// ...
Das war ziemlich unkompliziert, aber jetzt beginnt der eigentliche Spaß, da wir uns mit dem Kalenderraster beschäftigen werden. Halten wir kurz inne und überlegen, was wir wirklich tun müssen, um das richtig hinzubekommen.
Erstens wollen wir, dass die Datum-Nummern in den richtigen Wochentag-Spalten landen. Zum Beispiel ist der 1. Juli 2020 ein Mittwoch. Dort sollte die Datumszählung beginnen.
Wenn der erste Tag des Monats auf Mittwoch fällt, bedeutet das, dass wir in der ersten Woche leere Rasterelemente für Montag und Dienstag haben werden. Der letzte Tag des Monats ist der 31. Juli, der auf einen Freitag fällt. Das bedeutet, dass Samstag und Sonntag in der letzten Woche des Rasters leer sein werden. Wir wollen diese mit den vorangehenden und nachfolgenden Daten des vorherigen bzw. nächsten Monats füllen, damit das Kalenderraster immer voll ist.

Hinzufügen von Tagen für den aktuellen Monat
Um die Tage des aktuellen Monats zum Raster hinzuzufügen, müssen wir wissen, wie viele Tage im aktuellen Monat vorhanden sind. Das können wir mit der Methode daysInMonth von Day.js ermitteln. Erstellen wir dafür eine computed-Eigenschaft.
// CalendarMonth.vue
computed: {
// ...
numberOfDaysInMonth() {
return dayjs(this.selectedDate).daysInMonth();
}
}
Wenn wir das wissen, erstellen wir ein leeres Array mit einer Länge, die der Anzahl der Tage im aktuellen Monat entspricht. Dann map() wir dieses Array und erstellen für jeden einzelnen einen Tagesobjekt. Das erstellte Objekt hat eine beliebige Struktur, sodass Sie bei Bedarf weitere Eigenschaften hinzufügen können.
In diesem Beispiel benötigen wir jedoch eine date-Eigenschaft, die verwendet wird, um zu überprüfen, ob ein bestimmtes Datum der aktuelle Tag ist. Wir geben auch einen isCurrentMonth-Wert zurück, der prüft, ob das Datum im aktuellen Monat liegt oder außerhalb davon. Wenn es außerhalb des aktuellen Monats liegt, werden wir diese so gestalten, dass die Leute wissen, dass sie sich außerhalb des Bereichs des aktuellen Monats befinden.
// CalendarMonth.vue
computed: {
// ...
currentMonthDays() {
return [...Array(this.numberOfDaysInMonth)].map((day, index) => {
return {
date: dayjs(`${this.year}-${this.month}-${index + 1}`).format("YYYY-MM-DD")
isCurrentMonth: true
};
});
},
}
Hinzufügen von Tagen aus dem vorherigen Monat
Um Tage aus dem vorherigen Monat für die Anzeige im aktuellen Monat zu erhalten, müssen wir prüfen, welcher Wochentag des ersten Tages im ausgewählten Monat ist. Hier können wir das WeekDay-Plugin für Day.js verwenden. Erstellen wir dafür eine Hilfsmethode.
// CalendarMonth.vue
methods: {
// ...
getWeekday(date) {
return dayjs(date).weekday();
},
}
Dann müssen wir basierend darauf prüfen, welcher Tag der letzte Montag im vorherigen Monat war. Diesen Wert benötigen wir, um zu wissen, wie viele Tage aus dem vorherigen Monat in der aktuellen Monatsansicht sichtbar sein sollen. Dies können wir ermitteln, indem wir den Wochentagswert vom ersten Tag des aktuellen Monats abziehen. Wenn der erste Tag des Monats beispielsweise Mittwoch ist, müssen wir drei Tage abziehen, um den letzten Montag des vorherigen Monats zu erhalten. Mit diesem Wert können wir ein Array von Tagesobjekten erstellen, das vom letzten Montag des vorherigen Monats bis zum Ende dieses Monats reicht.
// CalendarMonth.vue
computed: {
// ...
previousMonthDays() {
const firstDayOfTheMonthWeekday = this.getWeekday(this.currentMonthDays[0].date);
const previousMonth = dayjs(`${this.year}-${this.month}-01`).subtract(1, "month");
// Cover first day of the month being sunday (firstDayOfTheMonthWeekday === 0)
const visibleNumberOfDaysFromPreviousMonth = firstDayOfTheMonthWeekday ? firstDayOfTheMonthWeekday - 1 : 6;
const previousMonthLastMondayDayOfMonth = dayjs(this.currentMonthDays[0].date).subtract(visibleNumberOfDaysFromPreviousMonth, "day").date();
return [...Array(visibleNumberOfDaysFromPreviousMonth)].map((day, index) = {
return {
date: dayjs(`${previousMonth.year()}-${previousMonth.month() + 1}-${previousMonthLastMondayDayOfMonth + index}`).format("YYYY-MM-DD"),
isCurrentMonth: false
};
});
}
}
Hinzufügen von Tagen aus dem nächsten Monat
Nun machen wir das Gegenteil und berechnen, welche Tage wir aus dem nächsten Monat benötigen, um das Raster für den aktuellen Monat zu füllen. Glücklicherweise können wir dieselbe Hilfe verwenden, die wir gerade für die Berechnung des vorherigen Monats erstellt haben. Der Unterschied besteht darin, dass wir berechnen, wie viele Tage aus dem nächsten Monat sichtbar sein sollen, indem wir diesen numerischen Wochentagswert von sieben abziehen.
Wenn beispielsweise der letzte Tag des Monats ein Samstag ist, müssen wir einen Tag von sieben abziehen, um ein Array von benötigten Tagen aus dem nächsten Monat (Sonntag) zu erstellen.
// CalendarMonth.vue
computed: {
// ...
nextMonthDays() {
const lastDayOfTheMonthWeekday = this.getWeekday(`${this.year}-${this.month}-${this.currentMonthDays.length}`);
const nextMonth = dayjs(`${this.year}-${this.month}-01`).add(1, "month");
const visibleNumberOfDaysFromNextMonth = lastDayOfTheMonthWeekday ? 7 - lastDayOfTheMonthWeekday : lastDayOfTheMonthWeekday;
return [...Array(visibleNumberOfDaysFromNextMonth)].map((day, index) => {
return {
date: dayjs(`${nextMonth.year()}-${nextMonth.month() + 1}-${index + 1}`).format("YYYY-MM-DD"),
isCurrentMonth: false
};
});
}
}
OK, wir wissen, wie wir alle benötigten Tage erstellen können, also verwenden wir sie und fügen alle Tage zu einem einzigen Array zusammen, das all die Tage enthält, die wir im aktuellen Monat anzeigen wollen, einschließlich Fülldaten aus dem vorherigen und nächsten Monat.
// CalendarMonth.vue
computed: {
// ...
days() {
return [
...this.previousMonthDays,
...this.currentMonthDays,
...this.nextMonthDays
];
},
}
Voilà, da haben wir es! Schauen Sie sich die finale Demo an, um alles zusammen zu sehen.
[...Array(this.numberOfDaysInMonth)]ist wahrscheinlich besser geschrieben alsdann kannst du den Tag-Parameter anstelle des Index verwenden.
Solider Artikel.
Wenn ich mich nicht irre, fehlt ein Schritt unter der Überschrift „Tage für den aktuellen Monat hinzufügen“. Das Erstellen der computed-Funktionen month() und year() ist nicht aufgeführt.
Toller Artikel, danke für die Teilung dieser aufschlussreichen Idee, Vue zu lernen!
Vielleicht den Teil hinzufügen, wo Sie die folgenden Funktionen in „CalendarMonth.vue“ hinzufügen, da ich sie aus Ihrem Sandbox-Link holen musste.
Vielen Dank nochmals und viel Spaß beim Codieren!
Der Kalender rendert die falschen Tage des Vormonats, wenn man zu November 2020 geht.
Kenneth, guter Hinweis! Behoben, danke für die Benachrichtigung.
Tolles Tutorial, aber was ist, wenn wir gleiche Zeilen von Tagen über jeden Monat hinweg haben wollen? Die aktuelle Einrichtung würde Ihnen eine unterschiedliche Anzahl von Zeilen pro Monat geben.
Auch die meisten Kalender, die ich sehe, beginnen mit Sonntag.
Wäre es auch möglich, eine Wochenansicht anstelle einer Monatsansicht zu erstellen und dann zur nächsten und vorherigen Woche zu navigieren?