vue2实现日历组件

738 阅读1分钟

日历面板为啥是6行7列呢?

7列:周一到周日共7天
6行:第一行肯定是显示当月的1号,如果这个月的1号为周六,这个月又有31天,那么必须是6行才能完全显示出当月的全部天数

思路

日历面板6*742天 由上个月天数+本月天数+下个月天数组成

下面以20227月为例子
1.上个月的天数
    我们需要先获取本月1号为周几,算出上个月在本月所占的天数(例如71号为周五,那么就说明我们需要为上月留5个位子)
    获取上个月的日期(例如6月一共有30天,那么我们循环5次并反转下数组,就可以获取[26,27,28,29,30]这5个日期)
2.本月
    直接获取这个月的天数
3.下个月
    日历面板共42天,42-(上个月天数+本月天数)然后用循环处理即可

代码如下:

在src目录下新增了utlis/index.js

//传入时间戳获取当前年月日
let getYearMonthDay = (timeStamp) => {
    let date = new Date(timeStamp)
    let year = date.getFullYear()
    let month = date.getMonth() + 1
    let day = date.getDate()
    let weekDay = date.getDay()
    return { year, month, day, weekDay }
}
//获取每个月1号为周几
let getStartMonthDay = (year, month) => {
    return new Date(year, month - 1, 1).getDay()
}
//获取每个月的天数
let getMonthCount = (year, month) => {
    return new Date(year, month, 0).getDate()
}
export { getYearMonthDay, getStartMonthDay, getMonthCount }

在src目录下新增了views/DemoPage.vue、views/el-calendar.vue

//DemoPage.vue
<template>
  <div>
    <elCalendar v-model="now" />
  </div>
</template>
<script>
import elCalendar from "./el-calendar.vue";
export default {
  components: {
    elCalendar,
  },
  data() {
    return {
        now:new Date().getTime()
    };
  },
};
</script>

<style></style>

//el-calendar.vue
<template>
  <div class="calendar" v-click-outside>
    <input
      type="text"
      :value="formatDate"
      placeholder="选择日期"
      class="calendar_input"
      ref="input"
    />
    <div class="calendar-body" v-if="isShow">
      <div class="calendar-header">
        <span class="preYear" @click="handlePreYear">&lt;&lt;</span>
        <span class="preMonth" @click="handlePreMonth">&lt;</span>
        <span class="header-time">{{ time[0] + "年" + time[1] + "月" }}</span>
        <span class="nextYear" @click="handleNextMonth">&gt;</span>
        <span class="nextMonth" @click="handleNextYear">&gt;&gt;</span>
      </div>
      <div class="calendar-table">
        <table class="calendar-inner-table">
          <tr>
            <th v-for="(item, index) in weekDay" :key="index">{{ item }}</th>
          </tr>
          <tr v-for="(item, index) in calendarList" :key="index">
            <td
              v-for="(i, ind) in item"
              :key="ind"
              :class="[
                i.myClassName,
                { currentDay: isCurrentDayStyle(i) },
                { selected: i.selected },
              ]"
              @click="selectExactDay(i)"
            >
              {{ i.day }}
            </td>
          </tr>
        </table>
      </div>
    </div>
  </div>
</template>

<script>
import * as util from "@/utlis/index";
export default {
  props: {
    value: {
      // type: Date||Number,
      type: Number,
      default: () => new Date(),
    },
  },
  directives: {
    // 这里是为了解决点击的时候 元素是否展示的问题
    clickOutside: {
      bind(el, binding, vnode) {
        let handler = (e) => {
          if (el.contains(e.target)) {
            if (!vnode.context.isShow) {
              // 包括
              vnode.context.isShow = true;
              e.target.focus();
            }
          } else {
            if (vnode.context.isShow) {
              // 不包括
              vnode.context.isShow = false;
              e.target.blur();
            }
          }
        };
        document.addEventListener("click", handler);
      },
      unbind(el) {
        document.removeEventListener("click", el.handler);
      },
    },
  },
  data() {
    return {
      time: [],
      isShow: false,
      weekDay: ["日", "一", "二", "三", "四", "五", "六"],
      calendarList: [],
      month: 0,
      year: 0,
      flag: false,
      inputVal: [],
    };
  },
  mounted() {
    this.getExactCurrentMonthDay(new Date().getTime());
  },
  computed: {
    formatDate() {
      const year = this.inputVal[0];
      const month =
        this.inputVal[1] < 10 ? "0" + this.inputVal[1] : this.inputVal[1];
      const day =
        this.inputVal[2] < 10 ? "0" + this.inputVal[2] : this.inputVal[2];
      //因为一开始没选中到时候 会出现undefined的问题所以多了一个判断
      return !year && !month && !day ? "" : `${year}-${month}-${day}`;
    },
  },
  methods: {
    //上一年
    handlePreYear() {
      let year = this.time[0];
      let month = this.time[1];
      year--;
      //这里得去更新全局time这个变量的值
      this.$set(this.time, 0, year);
      this.calendarList = this.combineData(year, month);
    },
    //下一年
    handleNextYear() {
      let year = this.time[0];
      let month = this.time[1];
      year++;
      this.$set(this.time, 0, year);
      this.calendarList = this.combineData(year, month);
    },
    //上个月
    handlePreMonth() {
      let year = this.time[0];
      let month = this.time[1];
      if (month > 1) {
        month--;
      } else {
        year--;
        month = 12;
      }
      //这里得去更新全局time这个变量的值
      this.$set(this.time, 0, year);
      this.$set(this.time, 1, month);
      this.calendarList = this.combineData(year, month);
    },
    //下个月
    handleNextMonth() {
      let year = this.time[0];
      let month = this.time[1];
      if (month > 11) {
        year++;
        month = 1;
      } else {
        month++;
      }
      this.$set(this.time, 0, year);
      this.$set(this.time, 1, month);
      this.calendarList = this.combineData(year, month);
    },
    //获取当前面板的具体日期(上个月+本月+下个月) 6*7
    getExactCurrentMonthDay(timeStamp) {
      //当天日期
      const { year, month, day } = util.getYearMonthDay(timeStamp);
      this.time = [year, month, day];
      //获取这个月显示的所有日期
      this.calendarList = this.combineData(year, month);
      //flag是为了还未选中时  做了个开关
      if (this.flag) {
        //选中日期的标识
        this.calendarList.forEach((item) => {
          item.forEach((i) => {
            if (i.year == year && i.month == month && i.day == day) {
              i.selected = true;
            }
          });
        });
      }
      this.flag = true;
    },
    //抽成一个函数 集中处理数据
    combineData(year, month) {
      //上个月在本月占位的个数(即这个月1号是周几,如果是周五,那么就说明上个月在本月占位是5个)
      let lastMonthDayCount = util.getStartMonthDay(year, month);
      //这里需要注意的是周一到周六对应的是1-6,而周日是0
      lastMonthDayCount = lastMonthDayCount == 0 ? 7 : lastMonthDayCount;
      //获取上个月的具体天数(即上个月最后一天的日期)
      const lastMonthDay = util.getMonthCount(year, month - 1);
      //组装后的上个月日期的数组对象
      const lastExactDate = this.getlastMonthDay(
        lastMonthDay,
        lastMonthDayCount,
        "lastMonthDay",
        year,
        month
      );
      //本月天数
      const currentMonthDayCount = util.getMonthCount(year, month);
      //组装后的本月日期的数组对象
      const currentExactDate = this.getMonthDay(
        currentMonthDayCount,
        "currentMothDay",
        year,
        month
      );
      //下个月占位的个数
      const nextMonthDayCount = 42 - (lastMonthDayCount + currentMonthDayCount);
      //组装后的下个月日期的数组对象
      const nextExactDate = this.getMonthDay(
        nextMonthDayCount,
        "nextMothDay",
        year,
        month
      );
      //获取这个月显示的所有日期(上个月+本月+下个月)
      const calendarData = [
        ...lastExactDate,
        ...currentExactDate,
        ...nextExactDate,
      ];
      //变成二维数组  为了方便渲染6行7列的数据
      const twoScaleArr = this.toTwoScaleArr(calendarData, 7);
      return twoScaleArr;
    },
    //生成天数的数据
    getMonthDay(count, className, year, month) {
      let nextMonthArr = [];
      for (let i = 1; i <= count; i++) {
        nextMonthArr.push({
          year: year,
          month: className == "currentMothDay" ? month : month + 1,
          day: i,
          myClassName: className,
          selected: false,
        });
      }
      return nextMonthArr;
    },
    getlastMonthDay(lastMonthDay, lastMonthDayCount, className, year, month) {
      let lastMonthDayArr = [];
      while (lastMonthDayCount > 0) {
        lastMonthDayArr.push({
          year: year,
          month: month - 1,
          day: lastMonthDay--,
          myClassName: className,
          selected: false,
        });
        lastMonthDayCount--;
      }
      return lastMonthDayArr.reverse();
    },
    // 一维数组转二维数组
    toTwoScaleArr(arr, num) {
      const len = Math.ceil(arr.length / num);
      let newArr = [];
      for (let i = 0; i < len; i++) {
        newArr.push(arr.slice(i * num, num * (i + 1)));
      }
      return newArr;
    },
    //判断是否为当天的样式
    isCurrentDayStyle(item) {
      const { year, month, day } = util.getYearMonthDay(new Date().getTime());
      const { year: y, month: m, day: d } = util.getYearMonthDay(
        new Date(item.year, item.month - 1, item.day).getTime()
      );
      return year == y && month == m && day == d;
    },
    //选中的样式
    selectExactDay(item) {
      const { year, month, day } = item;
      this.time = [year, month, day];
      this.inputVal = this.time;
      this.getExactCurrentMonthDay(
        new Date(this.time[0], this.time[1] - 1, this.time[2]).getTime()
      );
      this.isShow = false;
    },
  },
};
</script>

<style scoped>
.calendar {
  width: 322px;
  height: 312px;
  text-align: center;
  margin: 0 auto;
}
.calendar_input {
  height: 40px;
  line-height: 40px;
  outline: none;
  padding: 0 15px;
  width: 320px;
  outline: none;
  margin-left: -4px;
  text-indent: 5px;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  color: #606266;
  box-sizing: border-box;
}
.calendar_input:focus {
  outline: none;
  border-color: #409eff;
}
.calendar-body {
  margin-top: 10px;
  border: 1px solid #ddd;
  width: 322px;
  height: 312px;
  box-sizing: border-box;
  border-radius: 5px;
}
.calendar-header {
  height: 30px;
  line-height: 30px;
  display: flex;
  margin: 12px;
  text-align: center;
  cursor: pointer;
}
.calendar-header :hover {
  color: #409eff;
}
.preYear,
.preMonth,
.nextYear,
.nextMonth {
  color: #606266;
  user-select: none;
}
.header-time {
  display: inline-block;
  width: 80%;
  text-align: center;
  color: #606266;
}
.preMonth {
  margin-left: 20px;
}
.nextYear {
  margin-right: 20px;
}
.calendar-table {
  width: 100%;
  border-collapse: collapse;
}
.calendar-table th {
  padding: 5px;
  width: 41px;
  height: 41px;
  box-sizing: border-box;
  color: #606266;
}
.calendar-table td {
  color: #606266;
  user-select: none;
}
.calendar-inner-table {
  width: 100%;
}
.calendar-inner-table td {
  width: 32px;
  height: 32px;
  padding: 4px 0;
  box-sizing: border-box;
  text-align: center;
  cursor: pointer;
}
.calendar-inner-table tr:nth-child(1) {
  margin-bottom: 5px;
}
.calendar-inner-table .lastMonthDay,
.calendar-inner-table .nextMothDay {
  color: #c0c4cc !important;
}
.calendar-table .currentDay {
  color: #409eff;
}
.calendar-table .selected {
  color: #fff;
  background-color: #409eff;
  border-radius: 50%;
  display: block;
  margin: 0 auto;
}
</style>

image.png