export const weekMapZh = ['日', ' 一', '二', '三', '四', '五', '六'];
const calendarGrid = 42; // 7 * 6宫格;
export interface CalendarItem {
year: number;
month: number;
day: number;
isCurrentMonth: boolean;
}
// 是否为闰年
const isLeap = (year: number) => {
return (year % 4 === 0 && year % 100 !== 0) || year % 100 === 0;
};
// 获取[month]月有几天
const getDays = (year: number, month: number): number => {
const feb = isLeap(year) ? 29 : 28;
const daysPerMonth = [31, feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
return daysPerMonth[month];
};
// 获取下个月/上个月有多少天
const getNextOrLastMonthDays = (date: Date, type: 'next' | 'last') => {
const month = date.getMonth();
const year = date.getFullYear();
if (type === 'last') {
const lastMonth = month === 0 ? 11 : month - 1;
const lastYear = lastMonth === 11 ? year - 1 : year;
return {
year: lastYear,
month: lastMonth,
days: getDays(lastYear, lastMonth),
};
}
const nextMonth = month === 11 ? 0 : month + 1;
const nextYear = nextMonth === 0 ? year + 1 : year;
return {
year: nextYear,
month: nextMonth,
days: getDays(nextYear, nextMonth),
};
};
export const generateCalendar = (date: Date) => {
const currentYear = date.getFullYear();
const currentMonth = date.getMonth();
// 当月天数
const days = getDays(currentYear, currentMonth);
// 获取上月末尾天数和下月开头的天数,用于填补当月日历空白
const { days: lastMonthDays, year: lastMonthYear, month: lastMonth } = getNextOrLastMonthDays(date, 'last');
const { year: nextMonthYear, month: nextMonth } = getNextOrLastMonthDays(date, 'next');
// 1号是星期几
const weekIndex = new Date(`${currentYear}/${currentMonth + 1}/1`).getDay();
// 显示在当月末尾的下月天数
const trailDays = calendarGrid - weekIndex - days;
let trailVal = 0;
const calendarTable: CalendarItem[] = [];
for (let i = 0; i < calendarGrid; i++) {
// 补充上月天数
if (i < weekIndex) {
calendarTable[i] = {
year: lastMonthYear,
month: lastMonth,
day: lastMonthDays - weekIndex + i + 1,
isCurrentMonth: false,
};
// 补充下月天数
} else if (i >= days + weekIndex) {
if (trailVal < trailDays) {
trailVal += 1;
}
calendarTable[i] = {
year: nextMonthYear,
month: nextMonth,
day: trailVal,
isCurrentMonth: false,
};
}
}
// 填充当月日期
for (let d = 1; d <= days; d++) {
calendarTable[weekIndex + d - 1] = {
year: currentYear,
month: currentMonth,
day: d,
isCurrentMonth: true,
};
}
return calendarTable;
};
<template>
<div class="calendar">
<div class="calendar-operate">
<div class="button-group">
<button class="button" @click="changeMonth('prev')">
<i class="icon ri-arrow-left-s-line"></i>
</button>
<button class="button" @click="changeMonth('next')">
<i class="icon ri-arrow-right-s-line"></i>
</button>
</div>
<div class="calendar-operate__title">{{ dateText }}</div>
<button class="button" :disabled="isToday" @click="currentDate">今天</button>
</div>
<div class="calendar-header">
<span
v-for="(item, index) in weekMapZh"
:key="index"
class="calendar-header__item"
:class="{ gray: index === 0 || index === 6 }"
>{{ item }}</span
>
</div>
<div class="calendar-content" :data-month="date.getMonth() + 1">
<div
v-for="(item, index) in calendarTable"
:key="index"
class="calendar-content__item"
:class="[{ light: !item.isCurrentMonth }, { active: isActive(item) }]"
>
{{ item.day }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { weekMapZh, generateCalendar } from './calendar';
import { isAllTrue } from '@/utils/common';
import { CalendarItem } from './calendar';
const date = ref<Date>(new Date());
const calendarTable = computed(() => generateCalendar(date.value));
const dateText = computed(() => {
return `${date.value.getFullYear()}/${date.value.getMonth() + 1}`;
});
const isToday = computed(() => {
const current = new Date();
const validArr = [
date.value.getFullYear() === current.getFullYear(),
date.value.getMonth() === current.getMonth(),
date.value.getDay() === current.getDay(),
];
return isAllTrue(validArr);
});
/**
* 当天日期高亮显示, 兼容切换日期:
* 年月日都要对上才能高亮
* ps: 日历可能会显示下月/上月的同样日期, 仅当月日期高亮
*/
const isActive = (item: CalendarItem) => {
return isAllTrue([
item.day === date.value.getDate(),
item.isCurrentMonth,
item.month === new Date().getMonth(),
item.year === new Date().getFullYear(),
]);
};
// 切换到今天
const currentDate = () => {
date.value = new Date();
};
// 切换月份, 上个月 or 下个月
const changeMonth = (type: 'prev' | 'next'): void => {
let month = 0;
let year = 1970;
if (type === 'prev') {
month = date.value.getMonth() === 0 ? 11 : date.value.getMonth() - 1;
year = month === 11 ? date.value.getFullYear() - 1 : date.value.getFullYear();
} else {
month = date.value.getMonth() === 11 ? 0 : date.value.getMonth() + 1;
year = month === 0 ? date.value.getFullYear() + 1 : date.value.getFullYear();
}
if (month === new Date().getMonth()) {
currentDate();
return;
}
date.value.setDate(1);
date.value.setMonth(month);
date.value.setFullYear(year);
date.value = new Date(date.value);
};
</script>
<style lang="scss" scoped>
$gap: 8px;
$sub-active-color: #dbf0ff;
$active-color: #0065ff;
$gray: #979797;
.button {
height: 28px;
font-size: 12px;
background: #fff;
margin: 0;
padding: 0 16px;
border: 1px solid rgba($gray, 0.4);
border-radius: 14px;
cursor: pointer;
&:hover {
color: $active-color;
}
&:active {
background-color: rgba(0, 0, 0, 6%);
}
&:disabled {
color: $gray;
background-color: rgba(0, 0, 0, 6.5%);
cursor: not-allowed;
}
}
.button-group {
.button:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.button:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 0;
}
.button:not(:first-child):not(:last-child) {
border-radius: 0;
border-left: 0;
}
}
.icon {
height: 100%;
font-size: 24px;
display: inline-flex;
align-items: center;
}
.calendar-operate {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid rgba($gray, 0.15);
font-size: 18px;
position: relative;
&__title {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
margin: 0 8px;
}
}
.calendar-header {
margin-top: 6px;
padding: 8px 0;
font-size: 14px;
font-weight: bold;
display: flex;
&__item {
flex: 1;
text-align: right;
border-radius: 1px;
&.gray {
color: $gray;
font-weight: normal;
}
}
}
.calendar-content {
display: flex;
flex-wrap: wrap;
margin: 20px 0;
position: relative;
color: #333;
&::after {
content: attr(data-month);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 10em;
font-weight: bold;
color: rgba($gray, 0.1);
}
&__item {
height: 6em;
flex: calc(14.2% - $gap);
font-size: 14px;
box-sizing: border-box;
transition: all 0.2s ease;
text-align: right;
padding: 10px 0;
margin-right: $gap;
&:nth-child(7n),
&:nth-child(7n-6) {
color: $gray;
}
&:nth-child(7n) {
margin-right: 0;
}
&.active {
color: $active-color;
font-weight: bold;
border-bottom: 2px solid $active-color;
}
&:hover {
background-color: rgba($sub-active-color, 0.4);
cursor: pointer;
}
&.light {
color: rgba($gray, 0.4);
cursor: not-allowed;
}
}
}
</style>
标签:const,color,value,month,year,date,calendar
From: https://www.cnblogs.com/chyshy/p/16989890.html