<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>

Logo

前往低代码交流专区

更多推荐