日歷組件
需求
- 需要展示一個(gè)簡(jiǎn)潔的日歷,支持切換展示的年份、月份,支持切換選中日期
- 特殊日期展示小紅點(diǎn),比如如果當(dāng)日有日程,可以用小紅點(diǎn)標(biāo)注
效果展示
思路
- 表頭:
上一年、上一月、當(dāng)前月份展示、下一月、下一年
- 表體:
7列:["日", "一", "二", "三", "四", "五", "六"]
6行:一共必須包含本月所有日期,會(huì)有兩種情況:(1)5行能展示完全(2)6行能展示完全,兼容兩種情況選擇無論什么情況都展示6行
想清楚以上,思路就非常清晰了,我們只需要找到表格中的第一個(gè)日期,然后42個(gè)格子每個(gè)格子依次往后推一天就可以了
import dayjs from "dayjs";
const getDateList = () => {
const firstDay = showDate.value.dayjs.startOf("month").startOf("week");
const rows = 6;
return Array(rows * 7)
.fill(0)
.map((n, i) => {
const day = firstDay.add(i, "day");
return getCalendarDate(day);
});
};
- 選中的日期:我們需要有一個(gè)值來記錄選中的日期,方便高亮展示也方便后面切換時(shí)根據(jù)選中的日期展示當(dāng)前月份,所以定義了一個(gè)對(duì)象,默認(rèn)是選中當(dāng)前日期
const getCalendarDate = (dayjs) => {
return {
year: dayjs.year(),
month: dayjs.month(),
date: dayjs.date(),
value: dayjs.format(props.format),
dayjs: dayjs
};
};
const currentDate = ref(getCalendarDate(dayjs()));
- 展示的日期:因?yàn)榭梢郧袚Q上一年、上一月、下一月、下一年,所以展示的日期會(huì)存在和選中的日期不同的情況,也需要一個(gè)對(duì)象來記錄,默認(rèn)情況下展示的日期等于選中的日期
const showDate = ref(currentDate.value)
- 切換月份、年份:有了上面的這些,實(shí)現(xiàn)起來就非常容易,修改展示日期,然后根據(jù)展示日期重新獲取6*7的日期列表
const nextMonth = (number) => {
showDate.value = getCalendarDate(showDate.value.dayjs.add(number, "month"));
dateList.value = getDateList();
};
const nextYear = (number) => {
showDate.value = getCalendarDate(showDate.value.dayjs.add(number, "year"));
dateList.value = getDateList();
};
- 修改日期:同樣只需要修改選中日期、展示日期,然后根據(jù)展示日期重新獲取日期列表,同時(shí)拋出一個(gè)change事件,給外部使用
const emit = defineEmits(["change"]);
const handleChangeCurrent = (dayjs: Dayjs) => {
currentDate.value = getCalendarDate(dayjs);
showDate.value = currentDate.value;
dateList.value = getDateList();
emit("change", currentDate.value);
};
- 小紅點(diǎn)、非當(dāng)前月日期灰色展示:這些就是一些簡(jiǎn)單的判斷了,根據(jù)展示日期和選中日期來判斷就行
<td v-for="date in dateList.slice((row - 1) * 7, (row - 1) * 7 + 7)" :key="date.value">
<div
class="calendar-date hand"
:class="{
'calendar-date--grey': date.month !== showDate.month,
'calendar-date--today': date.value === currentDate.value
}"
@click="handleChangeCurrent(date.dayjs)"
>
<span>{{ date.date }}</span>
<span v-if="props.dotDays.includes(date.value)" class="calendar-dot"></span>
</div>
</td>
源碼
<template>
<div class="calendar">
<div class="calendar-header">
<div class="calendar-header__left">
<el-icon class="hand" @click="nextYear(-1)"><DArrowLeft /></el-icon>
<el-icon class="hand" @click="nextMonth(-1)"><ArrowLeft /></el-icon>
</div>
<div class="calendar-header__center">{{ showDate.year }} 年 {{ showDate.month + 1 }} 月</div>
<div class="calendar-header__right">
<el-icon class="hand" @click="nextMonth(1)"><ArrowRight /></el-icon>
<el-icon class="hand" @click="nextYear(1)"><DArrowRight /></el-icon>
</div>
</div>
<div class="calendar-main">
<table class="calendar-table">
<thead class="calendar-thead">
<tr>
<th v-for="(day, index) in sevenDay" :key="index">{{ day }}</th>
</tr>
</thead>
<tbody class="calendar-tbody">
<tr v-for="row in rows" :key="row">
<td v-for="date in dateList.slice((row - 1) * 7, (row - 1) * 7 + 7)" :key="date.value">
<div
class="calendar-date hand"
:class="{
'calendar-date--grey': date.month !== showDate.month,
'calendar-date--today': date.value === currentDate.value
}"
@click="handleChangeCurrent(date.dayjs)"
>
<span>{{ date.date }}</span>
<span v-if="props.dotDays.includes(date.value)" class="calendar-dot"></span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script lang="ts" setup>
import dayjs, { Dayjs } from "dayjs";
interface CalendarDate {
year: number;
month: number;
date: number;
value: string;
dayjs: Dayjs;
}
interface Props {
dotDays?: string[]; // 需要加點(diǎn)的日期
format?: string; // 日期格式,必須保證格式化后的日期是唯一的,默認(rèn)YYYY-MM-DD(參考dayjs)
current?: string; // 當(dāng)前選中的日期,格式與format保持一致,默認(rèn)選擇當(dāng)前日期
disabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
dotDays: () => [], // ["2023-09-19", "2023-09-20", "2023-08-31"]
format: "YYYY-MM-DD",
disabled: false
});
const sevenDay: Array<string> = ["日", "一", "二", "三", "四", "五", "六"];
const dateList = ref<CalendarDate[]>([]);
const getCalendarDate = (dayjs: Dayjs): CalendarDate => {
return {
year: dayjs.year(),
month: dayjs.month(),
date: dayjs.date(),
value: dayjs.format(props.format),
dayjs: dayjs
};
};
// 選中的日期
const currentDate = ref<CalendarDate>(getCalendarDate(dayjs()));
// 當(dāng)前日歷展示的日期
const showDate = ref<CalendarDate>(currentDate.value);
// 獲取日期列表,6*7天
const rows = 6;
const getDateList = (): Array<CalendarDate> => {
const firstDay = showDate.value.dayjs.startOf("month").startOf("week");
return Array(rows * 7)
.fill(0)
.map((n, i) => {
const day = firstDay.add(i, "day");
return getCalendarDate(day);
});
};
const nextMonth = (number: number) => {
showDate.value = getCalendarDate(showDate.value.dayjs.add(number, "month"));
dateList.value = getDateList();
};
const nextYear = (number: number) => {
showDate.value = getCalendarDate(showDate.value.dayjs.add(number, "year"));
dateList.value = getDateList();
};
// 修改選中日期
const emit = defineEmits(["change"]);
const handleChangeCurrent = (dayjs: Dayjs) => {
if (props.disabled) return;
currentDate.value = getCalendarDate(dayjs);
showDate.value = currentDate.value;
dateList.value = getDateList();
emit("change", currentDate.value);
};
onMounted(() => {
if (props.current) {
handleChangeCurrent(dayjs(props.current, props.format));
} else {
dateList.value = getDateList();
}
});
</script>
<style lang="scss" scoped>
.calendar {
user-select: none;
.calendar-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 22px;
padding: 0 12px;
font-size: 14px;
color: var(--el-text-color-primary);
}
.calendar-main {
padding-bottom: 12px;
margin-top: 12px;
}
.calendar-table {
width: 100%;
th,
td {
// width: (100/7);
width: 14.285%;
min-width: 40px;
padding: 4px 0;
font-size: 14px;
font-weight: 400;
line-height: 24px;
color: var(--el-text-color-primary);
text-align: center;
}
th {
color: #bfbfbf;
}
}
.calendar-date {
position: relative;
width: 24px;
height: 24px;
margin: -1px auto;
border-radius: 2px;
&--grey {
color: #bfbfbf;
}
&:hover {
background: var(--el-color-primary-light-9);
}
&--today {
color: #ffffff !important;
background: var(--el-color-primary) !important;
}
}
.calendar-dot {
position: absolute;
top: -3px;
right: -3px;
width: 6px;
height: 6px;
background-color: #fc474c;
border-radius: 50%;
}
}
.hand {
cursor: pointer;
}
</style>
|