Vue 封装一个日历组件

3,420 阅读2分钟

Vue 封装一个日历组件

先放上效果图,样式比较随意,大家看懂文章思路,可以自己去改。

这是用在公司内部sass系统里面的

这是element-ui的组件

写日历,首先要发现他的数据排版规律,如同小学生找规律一样。

  • 为了整体日历高度不随着月份的变化,高度发生变化,一般都是设计为 6 行 ,包含了上个月和下个月的天数(一般都是设置为灰色)
  • 找到每个月的第一天是周几,如上图所示,2020-11-01 是周日,getDay = 0 , 前面就会展示上个月一共 7 - 0 = 7 的天数,尾部数据就是 42 - 前面一共的天数,一般你不需要可以算尾部天数的

自己封装的一些简易的时间函数,为了防止在主程序中写入过多代码

function getYearMonthDay (value) {
  const date = new Date(value)
  const year = date.getFullYear()
  const month = date.getMonth()
  const day = date.getDate()
  return { year, month, day }
}

function getDate (year, month, day = 1) {
  return new Date(year, month, day)
}

export default {
  getYearMonthDay,
  getDate
}

日历中展示的可见天数, 模板中只要for循环遍历 visibleDaysok

computed: {
    visibleDays() {
        // 获取当月的第一天
        const currentFirstDay = utils.getDate(this.timer.year, this.timer.month, 1)
        // 获取是周几  就往前移动几天
        let week = currentFirstDay.getDay()
        // 日历当前月份的开始天数  如果是周日,就往前移动 7 天
        week = week === 0 ? 7 : week
        const startDay = currentFirstDay - week * 60 * 60 * 1000 * 24
        // 循环42天  6 行 7 列
        const arr = []
        for (let i = 0; i < 42; i++) {
            arr.push(new Date(startDay + i * 60 * 60 * 1000 * 24))
        }
        return arr
    }
}

模板中怎么展示这visibleDays 数据,这也是一个找规律的问题

 <div v-for="i in 6" :key="i">
     <span 
         v-for="j in 7" 
         :key="j"
         class="cell"
         :class="[
           { notCurrentMonth: !isCurrentMonth(visibleDays[(i -1) * 7 + (j - 1)]) },
           { today: isToday(visibleDays[(i -1) * 7 + (j - 1)]) }
         ]"
         @click="selectDay(visibleDays[(i -1) * 7 + (j - 1)])">
         <span :class="{ selected: isSelect(visibleDays[(i -1) * 7 + (j - 1)]) }">{{ visibleDays[(i -1) * 7 + (j - 1)].getDate() }}</span>
     </span>
</div>

完整组件代码

<template>
  <div v-click-outsize>
    <input :value="formDate" type="text" />
    <div v-if="isVisible" class="panel">
      <div class="panel-nav">
        <span @click="pervMonth"> &lt;&lt; </span>
        <span>{{ timer.year }} 年 {{ timer.month + 1 }} 月 </span>
        <span @click="nextMonth"> &gt;&gt; </span>
      </div>
      <div class="panel-content">
        <p class="panel-week">
          <span v-for="w in week" :key="'-' + w">{{ w }}</span>
        </p>
        <!-- 类似 9 * 9 乘法表 -->
        <div v-for="i in 6" :key="i">
          <span 
            v-for="j in 7" 
            :key="j"
            class="cell"
            :class="[
              { notCurrentMonth: !isCurrentMonth(visibleDays[(i -1) * 7 + (j - 1)]) },
              { today: isToday(visibleDays[(i -1) * 7 + (j - 1)]) }
            ]"
            @click="selectDay(visibleDays[(i -1) * 7 + (j - 1)])">
              <span :class="{ selected: isSelect(visibleDays[(i -1) * 7 + (j - 1)]) }">{{ visibleDays[(i -1) * 7 + (j - 1)].getDate() }}</span>
          </span>
        </div>
      </div>
      <div class="panel-footer"></div>
    </div>
    <div>努力学习,天天向上</div>
  </div>
</template>

<script>
import utils from '../utils/index.js'

export default {
  name: 'DatePicker',
  directives: {
    clickOutsize: {
      bind(el, binding, vnode) {
        const handler = (e) => {
          if (el.contains(e.target)) {
            if (!vnode.context.isVisible) {
              vnode.context.focus()
            }
          } else {
            if (vnode.context.isVisible) {
              vnode.context.blur()
            }
          }
        }
        el.handler = handler
        document.addEventListener('click', handler)
      },
      unbind(el) {
        document.removeEventListener('ckick', el.handler)
      }
    }
  },
  props: {
    value: {
      type: [Date, String]
    }
  },
  computed: {
    formDate() {      
      const { year, month, day } = utils.getYearMonthDay(this.value)
      return `${year}-${month + 1}-${day}`
    },
    visibleDays() {
      // 获取当月的第一天
      const currentFirstDay = utils.getDate(this.timer.year, this.timer.month, 1)
      // 获取是周几  就往前移动几天
      let week = currentFirstDay.getDay()
      // 日历当前月份的开始天数  如果是周日,就往前移动 7 天
      week = week === 0 ? 7 : week
      const startDay = currentFirstDay - week * 60 * 60 * 1000 * 24
      // 循环42天  6 行 7 列
      const arr = []
      for (let i = 0; i < 42; i++) {
        arr.push(new Date(startDay + i * 60 * 60 * 1000 * 24))
      }
      return arr
    }
  },
  data() {
    return {
      isVisible: false,
      timer: {
        year: '',
        month: ''
      },
      week: '日一二三四五六'
    }
  },
  mounted() {
    const { year, month, day } = utils.getYearMonthDay(this.value)
    this.timer.year = year
    this.timer.month = month
  },
  methods: {
    focus() {
      this.isVisible = true
    },
    blur() {
      this.isVisible = false
    },
    isCurrentMonth(date) {
      const { year, month } = utils.getYearMonthDay(utils.getDate(this.timer.year, this.timer.month, 1))
      const { year: y, month: m } = utils.getYearMonthDay(date)
      return year === y && month === m
    },
    isToday(date) {
      const { year, month, day } = utils.getYearMonthDay(new Date)
      const { year: y, month: m, day: d } = utils.getYearMonthDay(date)
      return year === y && month === m && day === d
    },
    selectDay(date) {
      this.timer = utils.getYearMonthDay(date)
      this.$emit('input', date)
      this.blur()
    },
    isSelect(date) {
      const { year, month, day } = utils.getYearMonthDay(this.value)
      const { year: y, month: m, day: d } = utils.getYearMonthDay(date)
      return year === y && month === m && day === d
    },
    pervMonth() {
      const d = utils.getDate(this.timer.year, this.timer.month, 1)
      d.setMonth(--this.timer.month)
      this.timer = utils.getYearMonthDay(d)
    },
    nextMonth() {
      const d = utils.getDate(this.timer.year, this.timer.month, 1)
      d.setMonth(++this.timer.month)
      this.timer = utils.getYearMonthDay(d)
    }
  }

}
</script>

<style lang="scss" scoped>
* {
  padding: 0;
  margin: 0;
}
.panel {
  position: absolute;
  background-color: #fff;
  box-shadow: 0px 0px 10px 0px rgba(153, 153, 153, 0.5);
  .panel-nav {
    text-align: center;
    span:first-child, span:last-child {
      cursor: pointer;
    }
  }
  .panel-content {
    .panel-week {
      span {
        display: inline-block;
        width: 32px;
        height: 32px;
        text-align: center;
        line-height: 32px;
        font-weight: bold;
        font-size: 14px;
      }
    }
    .cell {
      display: inline-flex;
      justify-content: center;
      align-items: center;
      width: 32px;
      height: 32px;
      text-align: center;
      box-sizing: border-box;
      font-weight: bold;
      font-size: 14px;
      box-sizing: border-box;
      cursor: pointer;
      span {
        display: inline-block;
        width: 20px;
        height: 20px;
      }
      &:hover {
        opacity: .3;
      }
    }

    .notCurrentMonth {
      color: gray;
    }
    .today {
      color: #1890FF;
    }
    .selected {
      background-color: #1890FF;
      color: #fff;
      border-radius: 50%;
    }
  }
}
</style>

贴一个GitHub地址:有需要的可以直接粘贴复制,MySimpleCalendar.vue这个组件做成了响应式了。 github.com/HuDaDa0/cal…