<template>
<div id="picker-container">
<div class="picker-header">
<div class="pointer month" @click="lastYear">{{ '<' }}</div>
<div class="pointer month" @click="lastMonth">{{ '<' }}</div>
<div class="date">{{ currentDate }}</div>
<div class="pointer month" @click="nextMonth">{{ '>' }}</div>
<div class="pointer month" @click="nextYear">{{ '>' }}</div>
</div>
<div class="picker-date">
<ul class="picker-week">
<li v-for="(item, index) in weekday" :key="index">{{ item }}</li>
</ul>
<div class="calendar-container">
<ul class="calendar-date" v-for="(item, index) in calendarList" :key="index">
<li
class="day pointer"
:class="{ active: selectDate.includes(child.date) }"
:style="{ color: child.textColor }"
v-for="(child, i) in item"
:key="i"
@click="() => selectDay(child)"
>
{{ child.day }}
</li>
</ul>
</div>
</div>
</div>
</template>
<script lang="ts" setup name="picker">
import { ref, onMounted, defineProps, defineExpose, reactive, nextTick, toRef, toRaw } from 'vue';
const props = defineProps({
// mode 有多选和单选
mode: {
type: String,
// default: 'multip'
default: 'single'
},
data: {
type: Array,
default: () => {
return ['2023-03-18'];
}
}
});
// 触发父组件刷新之类的
const emits = defineEmits<{
(e: 'loadData'): void;
}>();
defineExpose();
const currentDate = ref<string>('');
const selectDate = ref<any[]>(props.data);
const date = reactive<Date>(new Date());
const weekday = ref<string[]>(['一', '二', '三', '四', '五', '六', '日']);
// 去年
function lastYear() {
yearFormat('lastMonth');
}
// 明年
function nextYear() {
yearFormat('nextMonth');
}
// 上个月
function lastMonth() {
monthFormat('lastMonth');
}
// 下个月
function nextMonth() {
monthFormat('nextMonth');
}
// 第一步:先获取日期,默认获取当前日期
function getDefaultHeaderDate() {
const year = date.getFullYear();
const month = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1;
const day = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate();
currentDate.value = `${year}-${month}-${day}`;
selectDate.value = [currentDate.value];
getCalendar();
}
// 获取天数
function getDays(year: number, month: number) {
// 每月的天数写在数组中,再判断时闰年还是平年确定2月分的天数
let days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)) {
days[1] = 29;
}
return days[month];
}
// // 获取天数
// function OtherGetDays(yearData?: number, monthData?: number) {
// const date = new Date();
// const year = yearData ?? date.getFullYear();
// const month = monthData ?? date.getMonth();
// const days = new Date(year, month + 1, 0).getDate();
// return days;
// }
// 获取每个月第一天是星期几
function getWeekDays(year: number, month: number): number {
var monthFirstWeekDay = new Date(year, month - 1, 1).getDay(); // 返回本月1号是星期几
return monthFirstWeekDay;
}
// 第二步:获取日期列表
const calendarList = ref<any>([]);
function getCalendar() {
calendarList.value = [];
const dateArray = currentDate.value.split('-');
if (!dateArray.length) return;
const year = Number(dateArray[0]);
const month = Number(dateArray[1]); // 减一 ,因为初始化已经加一了
const daysNumber = getDays(year, month - 1); //
// 本月全部天数
for (let i = 1; i <= daysNumber; i++) {
const day = i < 10 ? `0${i}` : i;
const item = {
date: `${dateArray[0]}-${dateArray[1]}-${day}`,
year: dateArray[0],
month: dateArray[1],
day: Number(day),
textColor: '#000'
};
calendarList.value.push(item);
}
// 拼接上个月日期数据
let firstWeekDay = getWeekDays(year, month);
firstWeekDay = firstWeekDay === 0 ? 6 : firstWeekDay - 1;
const lastMonth = preLastMonth(firstWeekDay);
calendarList.value.unshift(...lastMonth);
// 拼接下个月数据
const length = calendarList.value.length;
const nextMonth = preNextMonth(length);
calendarList.value.push(...nextMonth);
dateToWeek();
}
// 获取上个月的日期,并返回数据,添加到原本的数据结构中
function preLastMonth(days: number) {
const dateArray = currentDate.value.split('-');
let year = Number(dateArray[0]);
let month: string | number = Number(dateArray[1]);
month = month - 1 === 0 ? 12 : month - 1; // 同样减去 1 ,才能拿到上一个月
if (month === 12) {
year = Number(dateArray[0]) - 1;
}
let preDaysNumber = getDays(year, month - 1); // 某月总天数
month = month < 10 ? `0${month}` : month;
// 获取上个月的日期,循环几次,就代表从上个月拿多少天
const lastMonth = [];
while (lastMonth.length !== days) {
const day = preDaysNumber < 10 ? `0${preDaysNumber}` : preDaysNumber;
const item = {
date: `${year}-${month}-${day}`,
year,
month,
day: Number(day),
textColor: '#ccc',
type: 'last'
};
lastMonth.push(item);
preDaysNumber--;
}
return lastMonth.reverse();
}
// 获取下个月的日期,并返回数据,添加到原本的数据结构中
function preNextMonth(length: number) {
// 42 为固定数量,可以参考电脑的日历组件一个面板天数
const diff = 42 - length;
const dateArray = currentDate.value.split('-');
let year = Number(dateArray[0]);
let month: string | number = Number(dateArray[1]);
month = month === 12 ? 1 : month + 1; // 当前月加上一 等于下一个月
if (month === 1) {
year = Number(dateArray[0]) + 1;
}
month = month < 10 ? `0${month}` : month;
// 获取下个月的日期,循环几次,就代表从下个月拿多少天
const nextMonth = [];
let numberDays = 1;
while (nextMonth.length !== diff) {
const day = numberDays < 10 ? `0${numberDays}` : numberDays;
const item = {
date: `${year}-${month}-${day}`,
year,
month,
day: Number(day),
textColor: '#ccc',
type: 'next'
};
nextMonth.push(item);
numberDays++;
}
return nextMonth;
}
// 将天数转换成对应的周的日期
function dateToWeek() {
calendarList.value;
const array = [];
while (array.length !== 6) {
array.push(calendarList.value.splice(0, 7));
}
calendarList.value = array;
}
// 获取默认的日期
function monthFormat(type: string) {
let dateArray = currentDate.value.split('-');
let month = dateArray.length ? Number(dateArray[1]) : null;
if (!month) {
return new Error('当前月份是 null 啦 ~兄弟');
}
let monthData;
if (type === 'lastMonth') {
monthData = month - 1 === 0 ? 12 : month - 1;
if (monthData === 12) {
let beforeYear = Number(dateArray[0]) - 1;
dateArray.splice(0, 1, beforeYear);
}
} else {
monthData = month === 12 ? 1 : month + 1;
if (monthData === 1) {
let nextYear = Number(dateArray[0]) + 1;
dateArray.splice(0, 1, nextYear);
}
}
let results = monthData < 10 ? `0${monthData}` : monthData;
dateArray.splice(1, 1, results);
currentDate.value = dateArray.join('-');
getCalendar();
}
// 用户调整年份
function yearFormat(type: string) {
let dateArray = currentDate.value.split('-');
if (type === 'lastMonth') {
let beforeYear = Number(dateArray[0]) - 1;
dateArray.splice(0, 1, beforeYear);
} else {
let nextYear = Number(dateArray[0]) + 1;
dateArray.splice(0, 1, nextYear);
}
currentDate.value = dateArray.join('-');
getCalendar();
}
// 用户选中日期
function selectDay(record: { date: string; type: string }) {
if (record.type === 'last') {
lastMonth();
} else if (record.type === 'next') {
nextMonth();
}
currentDate.value = record.date;
const index = selectDate.value.findIndex((item) => item === record.date);
if (index !== -1) {
selectDate.value.splice(index, 1);
return;
}
if (props.mode === 'single') selectDate.value = [toRaw(record.date)];
else selectDate.value.push(record.date);
}
onMounted(() => {
getDefaultHeaderDate();
});
</script>
<style lang="scss" scoped>
#picker-container {
width: 100%;
.picker-header {
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
border-bottom: 1px solid #ccc;
padding-bottom: 10px;
box-sizing: border-box;
user-select: none;
}
.picker-date {
border: 1px solid #ccc;
border-top: none;
width: 100%;
// height: 300px;
.picker-week {
display: flex;
justify-content: space-around;
align-items: center;
list-style: none;
border-bottom: 1px solid #ccc;
}
.calendar-container {
width: 100%;
height: 100%;
.calendar-date {
height: 50px;
display: flex;
justify-content: space-around;
align-items: center;
list-style: none;
user-select: none;
.day {
width: 50px;
height: 50px;
text-align: center;
line-height: 50px;
transition: all 0.3s;
}
.active {
background-color: #00bfbf;
}
}
}
}
}
</style>
Vue3 自定义日期组件,可多选、单选
最新推荐文章于 2025-04-05 16:59:13 发布