手写vue日历组件

690 阅读1分钟

手写vue日历组件

vue版本vue2,样式和参数参考element-ui的Calendar组件。自动补全上一月和下一月空白日期,支持周起始日设置,单元格日期自定义插槽,自定义右侧头部插槽。 坑点:闰年2月29天处理

处理闰年问题代码

//判断是否为闰年
funtion isLeapYear(year) {
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}

//获取当月的天数
funtion getDaysOfMonth(dateStr) {
    var date = new Date(dateStr);
    var year = date.getFullYear();
    var mouth = date.getMonth() + 1;
    var day = 0;

    if (mouth == 2) {
    day = isLeapYear(year) ? 29 : 28;
    } else if (
    mouth == 1 ||
    mouth == 3 ||
    mouth == 5 ||
    mouth == 7 ||
    mouth == 8 ||
    mouth == 10 ||
    mouth == 12
    ) {
    day = 31;
    } else {
    day = 30;
    }
    return day;
},

完整代码

分为头部和日历天数区域,dom结构和样式代码 参数 v-model 绑定值 Date, String, Number first-day-of-week 周起始日 Number 默认值 1

<template>
  <div class="calendar">
    <div class="calendar-header">
      <div class="left">
        <span class="month">{{ value.toString("yyyy年MM月") }}</span>
        <button @click="btnChangeMonth('prevMonth')">上个月</button>
        <button style="margin-left: 10px" @click="btnChangeMonth('nextMonth')">
          下个月
        </button>
      </div>
      <div class="right">
        <!-- 自定义右侧头部插槽 -->
        <slot name="right-header"></slot>
      </div>
    </div>

    <div class="calendar-body">
      <table class="table">
        <thead>
          <tr>
            <td>
              {{ week[firstDayOfWeek] }}
            </td>
            <td
              v-for="item in week.length - 1 - firstDayOfWeek"
              :key="week[item + firstDayOfWeek]"
            >
              {{ week[item + firstDayOfWeek] }}
            </td>
            <td v-for="item in firstDayOfWeek" :key="week[item - 1]">
              {{ week[item - 1] }}
            </td>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(item, index) in days.length / 7" :key="index">
            <td
              :class="{ empty: !days[day + index * 7 - 1].date }"
              v-for="day in 7"
              :key="day + index * 7"
            >
              <!-- 单元格日期自定义插槽 -->
              <slot name="dateCell" :row="days[day + index * 7 - 1]">
                {{
                  days[day + index * 7 - 1] &&
                  days[day + index * 7 - 1].date.split("-")[2]
                }}
              </slot>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

<script>
export default {
  name: "Calendar",
  model: {
    prop: "value",
    event: "monthChange",
  },
  props: {
    value: [Date, String, Number],
    firstDayOfWeek: {
      type: Number,
      default: 1,
    },
  },
  data() {
    return {
      week: ["日", "一", "二", "三", "四", "五", "六"],
      days: [],
    };
  },
  methods: {
    btnChangeMonth(month) {
      // 上一月
      if (month === "prevMonth") {
        this.$emit("monthChange", this.getPrevNextMonth(this.value, -1));
      }
      // 下一月
      if (month === "nextMonth") {
        this.$emit("monthChange", this.getPrevNextMonth(this.value, 1));
      }
    },
    init() {
      const date = this.value;
      const time = date.toString("yyyy-MM");
      const firstDayOfWeek = this.firstDayOfWeek;
      // 当月周一是星期几 0-6 0是星期日
      const days = this.getMonthDay(date);
      const fillDay = [];
      let day = [];
      // 一个月多少天
      const monthDay = this.getDaysOfMonth(date);
      // 补全空格上月日期
      const fill = (7 + days - firstDayOfWeek) % 7;
      for (let i = 0; i < fill; i++) {
        fillDay.push({ date: "" });
      }

      for (let j = 1; j <= monthDay; j++) {
        let d = j.toString().padStart(2, "0");
        day.push({ date: time + "-" + d });
      }
      day = [...fillDay, ...day];
      // 补全空格下个月日期
      while (day.length % 7 != 0) {
        day.push({ date: "" });
      }
      this.$set(this, "days", day);
    },
    //获取当月的天数
    getDaysOfMonth(dateStr) {
      var date = new Date(dateStr);
      var year = date.getFullYear();
      var mouth = date.getMonth() + 1;
      var day = 0;

      if (mouth == 2) {
        day = this.isLeapYear(year) ? 29 : 28;
      } else if (
        mouth == 1 ||
        mouth == 3 ||
        mouth == 5 ||
        mouth == 7 ||
        mouth == 8 ||
        mouth == 10 ||
        mouth == 12
      ) {
        day = 31;
      } else {
        day = 30;
      }
      return day;
    },
    //判断是否为闰年
    isLeapYear(year) {
      return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
    },

    /**
     * 获取上一月或者下一个月
     * @param now 日期
     * @param addMonths 传-1 上个月,传1 下个月
     */
    getPrevNextMonth(now, addMonths) {
      var dd = new Date(now);
      var m = dd.getMonth() + 1;
      var y =
        dd.getMonth() + 1 + addMonths > 12
          ? dd.getFullYear() + 1
          : dd.getFullYear();
      if (m + addMonths == 0) {
        y = y - 1;
        m = 12;
      } else {
        if (m + addMonths > 12) {
          m = "01";
        } else {
          m = m + 1 < 10 ? "0" + (m + addMonths) : m + addMonths;
        }
      }
      return new Date(y, m, 0);
    },

    // 获取月份1号是周几
    getMonthDay(date) {
      var d = new Date(date);
      const y = d.getFullYear();
      const m = d.getMonth();
      return new Date(y, m, 1).getDay();
    },
  },
  created() {
    this.init();
  },
  watch: {
    value(val) {
      this.init();
    },
  },
};
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.calendar {
  width: 100%;
  background: #fff;
  .calendar-header {
    overflow: hidden;
    .left {
      float: left;
      .month {
        display: inline-block;
        width: 96px;
        font-size: 16px;
        line-height: 32px;
        font-weight: 500;
        color: #333;
      }
    }
    .right {
      float: right;
    }
  }
  .calendar-body {
    width: 100%;
    padding: 0 1px;
    overflow-y: auto;
    .table {
      width: 100%;
      border-collapse: collapse;
      box-sizing: border-box;
      thead {
        td {
          padding: 8px 0;
          text-align: center;
        }
      }
      tbody {
        td {
          width: 14.28%;
          height: 80px;
          border: 1px solid #ebeef5;
          font-size: 18px;
          padding: 8px 14px;
          color: #666;
          &.empty {
            background: #f9f9f9;
          }
        }
      }
    }
  }
}
</style>

用法

所有插槽和参数的用法

// 自定义左侧头部
<Calendar v-model="new Date()">
    <div slot="right-header" style="color: red">自定义左侧头部</div>
</Calendar>

// 自定义日期
<Calendar v-model="new Date()">
    <template slot="dateCell" slot-scope="scope">
    {{ scope.row.date.split("-").slice(1).join("-") }}
    </template>
</Calendar>

// 自定义周起始日
<Calendar v-model="new Date()" :first-day-of-week="0"> </Calendar>

demo

demo地址

我的博客和GitHub地址

github.com/lanpangzhi

blog.langpz.com