美文网首页
vue日历组件

vue日历组件

作者: 芸芸众生ing | 来源:发表于2021-09-29 17:22 被阅读0次

    效果图

    image.png

    组件代码

    <template>
      <div class="calendar">
        <slot name="header">
          <div class="top">
            <div class="current">{{ time }}</div>
            <div class="box">
              <div class="btn" @click="btn('prevMonth')">上个月</div>
              <div class="btn" @click="btn('currentMonth')">当前月</div>
              <div class="btn" @click="btn('nextMonth')">下个月</div>
            </div>
          </div>
        </slot>
        <div class="date">
          <div class="item">日</div>
          <div class="item">一</div>
          <div class="item">二</div>
          <div class="item">三</div>
          <div class="item">四</div>
          <div class="item">五</div>
          <div class="item">六</div>
          <template v-for="(item,index) in list">
            <div class="item" :class="{active:item.type === currentSelectDate,current:currentDate == item.type,grey:item.next||item.prve}" :key="item.type" @click="click(item)">
              <slot :index="index" :item="item">{{ item.date }}</slot>
            </div>
          </template>
        </div>
      </div>
    </template>
    <script>
    export default {
      name: "Calendar",
      props: {
        value: { type: Date, default: () => new Date() }
      },
      data() {
        return {
          list: [],
          time: "",
          currentSelectDate: "",
          currentDate: ""
        };
      },
      watch: {
        value(a) {
          if (a) this.time = a;
          this.btn(0);
        }
      },
      created() {
        this.btn();
      },
      methods: {
        click(item) {
          if (this.currentSelectDate === item.type) {
            // 取消选中
            this.currentSelectDate = "";
            // 选中日期
          } else this.currentSelectDate = item.type;
          // 选中日期是上个月
          if (item.next) this.btn("nextMonth");
          // 选中日期是下个月
          if (item.prve) this.btn("prevMonth");
          this.$emit("click", { ...item, selectDate: this.currentSelectDate });
          this.$emit("update:select-date", this.currentSelectDate);
        },
        // 默认当前月
        btn(type = "currentMonth") {
          // 获取指定时间的年份和月份
          let currentYear = new Date(this.time).getFullYear();
          let currentMonth = new Date(this.time).getMonth() + 1;
          // 点击上个月
          if (type === "prevMonth") {
            // 指定时间的月份为1月,上一月为上一年的12月
            if (currentMonth === 1) (currentMonth = 13), (currentYear = currentYear - 1);
            currentMonth--;
          }
          // 当前月
          if (type === "currentMonth") {
            // 获取当前时间的年月日
            currentYear = new Date().getFullYear();
            currentMonth = new Date().getMonth() + 1;
            let currentDate = new Date().getDate();
            this.currentDate = `${currentYear}-${currentMonth > 9 ? currentMonth : "0" + currentMonth}-${currentDate > 9 ? currentDate : "0" + currentDate}`;
          }
          // 下个月
          if (type === "nextMonth") {
            // 指定时间的月份为12月,下一月为下一年的1月
            if (currentMonth === 12) (currentMonth = 0), (currentYear = currentYear + 1);
            currentMonth++;
          }
          // 当前日历展示的年月
          this.time = `${currentYear}-${currentMonth > 9 ? currentMonth : "0" + currentMonth}`;
          let date = `${currentYear}-${currentMonth > 9 ? currentMonth : "0" + currentMonth}-01`;
          this.init(date);
          this.$emit("change-month", { currentYear, currentMonth, type, date });
          this.$emit("update:current-date", this.currentDate);
        },
        // 生成日历日期,因每月开始和结束不是周日和周六,需要取上月月末和下月月初补满每周七天,所以生成的总天数共35-42天。
        init(time) {
          // 获得指定时间年月,月总天数
          let date = new Date(time);
          let currentYear = date.getFullYear(); // 年
          let currentMonth = date.getMonth() + 1; // 月
          let currentMonthDate = new Date(currentYear, currentMonth, 0).getDate(); // 当月总天数
          let list = new Array(currentMonthDate).fill().map((_, i) => ({ year: currentYear, month: currentMonth, date: i + 1 }));
    
          // 获取上月,需要处理跨年
          let preMonth = currentMonth;
          if (preMonth === 1) (preMonth = 13), (currentYear = currentYear - 1);
          let beforeMonthDate = new Date(currentYear, preMonth - 1, 0).getDate(); // 上月总天数
          let beforeDate = new Date(`${currentYear}-${preMonth - 1}-${beforeMonthDate}`).getDay();
          for (let i = 0; i <= beforeDate; i++) list.unshift({ prve: true, year: currentYear, month: preMonth - 1, date: beforeMonthDate - i });
    
          // 获取下月,需要处理跨年
          let nextMonth = currentMonth;
          if (nextMonth === 12) (nextMonth = 0), (currentYear = currentYear + 1);
          let afterDate = 7 - ((currentMonthDate + beforeDate) % 7); // 下月月头
          for (let i = 1; i < afterDate; i++) list.push({ next: true, year: currentYear, month: nextMonth + 1, date: i });
          this.list = list.map((e, i) => {
            let { year, month, date } = e;
            return { ...e, week: Math.floor(i % 7), type: `${year}-${month > 9 ? month : "0" + month}-${date > 9 ? date : "0" + date}` };
          });
        }
      }
    };
    </script>
    <style lang='less' scoped>
    .calendar {
      min-height: 300px;
      .top {
        display: flex;
        justify-content: space-between;
        align-items: center;
        height: 40px;
        margin: 0 0 20px 0;
        padding: 0 10px;
      }
      .box {
        display: flex;
        justify-content: space-evenly;
        width: 160px;
        border: 1px solid #eee;
        border-radius: 5px;
        margin: 5px;
        color: #666;
        .btn {
          padding: 3px 5px;
          cursor: pointer;
          transition: all 0.1s;
          &:nth-child(2) {
            border: 1px solid #eee;
            border-top: none;
            border-bottom: none;
          }
          &:hover {
            color: #333;
            background: #eee;
            transition: all 0.1s;
          }
        }
      }
      .item {
        flex: 0 0 13%;
        height: 35px;
        margin: 2px;
        min-width: 20px;
        text-align: center;
        color: #444;
        padding-top: 5px;
        box-sizing: border-box;
        border-radius: 5px;
        transition: all 0.2s;
        &.grey {
          color: #999;
        }
        &:hover {
          transition: all 0.2s;
          cursor: pointer;
          background: #5164e7a0;
          color: #fff;
        }
        &.current {
          transition: all 0.2s;
          background: #5164e7a0;
          color: #fff;
        }
        &.active {
          transition: all 0.2s;
          background: #5164e7;
          color: #fff;
        }
      }
      .date {
        display: flex;
        justify-content: space-between;
        align-items: center;
        flex-wrap: wrap;
      }
    }
    </style>
    
    <template>
      <div class="calendar">
        <div class="head">
          <div class="top">
            <el-date-picker class="pick" ref="time" v-model="Time" type="date" placeholder="选择日期" :editable="false"
              :clearable="false" size="small" format="YYYY-MM-DD" value-format="YYYY-MM-DD" @change="handlerDate" />
            <div class="box">
              <div class="btn" @click="handlerDate('prevMonth')">上个月</div>
              <div class="btn" @click="handlerDate('currentMonth')">当前月</div>
              <div class="btn" @click="handlerDate('nextMonth')">下个月</div>
            </div>
          </div>
          <div class="bottom">
            <slot name="tips"> </slot>
          </div>
        </div>
        <div class="date">
          <div class="item">日</div>
          <div class="item">一</div>
          <div class="item">二</div>
          <div class="item">三</div>
          <div class="item">四</div>
          <div class="item">五</div>
          <div class="item">六</div>
          <template v-for="(item, index) in List" :key="item.time">
            <div class="item"
              :class="{ active: item.time === Time, current: currentDate == item.time, grey: item.next || item.prve }"
              @click="click(item)">
              <slot :index="index" :item="item">{{ item.date }}</slot>
            </div>
          </template>
        </div>
      </div>
    </template>
    <script setup lang="ts" name="Calendar">
    import { watch, toRefs, computed, reactive } from 'vue';
    const emit = defineEmits(['changeDate', 'update:currentDate', 'change-month'])
    const props = defineProps({
      currentDate: {
        type: String,
        default: ''
      }
    });
    const Data = reactive<{
      selectMonth: string,
      List: Array<Object>,
    }>({
      selectMonth: '',
      List: []
    })
    const { currentDate } = toRefs(props);
    const { selectMonth, List } = toRefs(Data)
    
    const Time = computed({
      set: (val) => emit("update:currentDate", val),
      get: () => currentDate.value
    })
    // 点击日历
    function click(item: Object) {
      let { year, month, date, week, time } = item;
      if (time) Time.value = time;
      if (month != selectMonth.value) {
        emit("change-month", month)
        handlerDate();
      }
      emit("changeDate");
    }
    // 处理日期时间
    function handlerDate(type?: any) {
      let date = Time.value ? new Date(Time.value) : new Date();
      if (type == 'currentMonth') date = new Date();
      // 获取指定时间的年份和月份
      let currentYear: string | number = date.getFullYear();
      let currentMonth: string | number = date.getMonth() + 1;
      let currentDate: string | number = date.getDate();
      // 点击上个月
      if (type === "prevMonth") {
        // 指定时间的月份为1月,上一月为上一年的12月
        if (currentMonth === 1) {
          currentMonth = 12;
          currentYear = currentYear - 1;
        } else {
          currentMonth--;
        }
      }
      // 下个月
      if (type === "nextMonth") {
        // 指定时间的月份为12月,下一月为下一年的1月
        if (currentMonth === 12) {
          currentMonth = 1;
          currentYear = currentYear + 1;
        } else {
          currentMonth++;
        }
      }
    
      currentMonth = currentMonth.toString().padStart(2, '0');
      currentDate = currentDate.toString().padStart(2, '0');
      // 当前日历展示的年月日    
      Time.value = `${currentYear}-${currentMonth}-${currentDate}`;
      selectMonth.value = currentMonth;
      init(currentYear, +currentMonth);
    }
    // 生成日历日期,因每月开始和结束不是周日和周六,需要取上月月末和下月月初补满每周七天,所以生成的总天数共35-42天。
    function init(year: number, month: number) {
      // 获得指定时间年月,月总天数
      let date = new Date(year, month - 1);
      let currentYear = date.getFullYear(); // 年
      let currentMonth = date.getMonth() + 1; // 月
      let currentMonthDate = new Date(currentYear, currentMonth, 0).getDate(); // 当月总天数
      let list: Array<Object> = new Array(currentMonthDate).fill(1).map((_, i) => ({ year: currentYear, month: currentMonth, date: i + 1 }));
    
      // 获取上月,需要处理跨年
      let preMonth = currentMonth;
      let preYear = currentYear;
      if (preMonth === 1) (preMonth = 13), (preYear = preYear - 1);
      let beforeMonthDate = new Date(currentYear, preMonth - 1, 0).getDate(); // 上月总天数
      let beforeDate = new Date(`${currentYear}-${preMonth - 1}-${beforeMonthDate}`).getDay(); //获取上月最后一天是周几
      for (let i = 0; i <= beforeDate; i++) list.unshift({ prve: true, year: preYear, month: preMonth - 1, date: beforeMonthDate - i });
    
      // 获取下月,需要处理跨年
      let nextMonth = currentMonth;
      let nextYear = currentYear;
      if (nextMonth === 12) (nextMonth = 0), (nextYear = nextYear + 1);
      // let afterDate = 7 - ((currentMonthDate + beforeDate) % 7); // 下月月头,按周填充补满
      let afterDate = 42 - (currentMonthDate + beforeDate); // 下月月头,按照每月5周填充
      for (let i = 1; i < afterDate; i++) list.push({ next: true, year: nextYear, month: nextMonth + 1, date: i });
    
      List.value = list.map((e, i) => {
        let { year, month, date } = e;
        return { year, month, date, week: Math.floor(i % 7), time: `${year}-${month.toString().padStart(2, 0)}-${date.toString().padStart(2, 0)}` };
      });
    
    }
    handlerDate()
    </script>
    <style lang='scss' scoped>
    .calendar {
      min-height: 360px;
      overflow: hidden;
    
      .head {
        margin: 0 0 15px 0;
        padding: 0 10px;
    
        .top {
          display: flex;
          justify-content: space-between;
          align-items: center;
    
          & :deep(.pick.el-input) {
            width: 80px;
    
            .el-input__prefix,
            .el-input__suffix {
              display: none;
            }
    
            .el-input__wrapper {
              box-shadow: none;
              text-align: left;
              padding-left: 0;
              padding-right: 0;
            }
          }
        }
    
        .bottom {
          height: 35px;
          line-height: 35px;
        }
      }
    
      .box {
        display: flex;
        justify-content: space-evenly;
        width: 160px;
        border: 1px solid #eee;
        border-radius: 5px;
        margin: 5px;
        color: #666;
    
        .btn {
          padding: 3px 5px;
          cursor: pointer;
          transition: all 0.1s;
    
          &:nth-child(2) {
            border: 1px solid #eee;
            border-top: none;
            border-bottom: none;
          }
    
          &:hover {
            color: #333;
            background: #eee;
            transition: all 0.1s;
          }
        }
      }
    
      .item {
        flex: 0 0 13%;
        height: 35px;
        margin: 2px;
        min-width: 20px;
        text-align: center;
        color: #444;
        padding-top: 5px;
        box-sizing: border-box;
        border-radius: 5px;
        transition: all 0.2s;
    
        &.grey {
          color: #999;
        }
    
        &:hover {
          transition: all 0.2s;
          cursor: pointer;
          background: #5164e7a0;
          color: #fff;
        }
    
        &.current {
          transition: all 0.2s;
          background: #5164e7a0;
          color: #fff;
        }
    
        &.active {
          transition: all 0.2s;
          background: #5164e7;
          color: #fff;
        }
      }
    
      .date {
        display: flex;
        justify-content: space-between;
        align-items: center;
        flex-wrap: wrap;
      }
    }
    </style>
    
    
    

    相关文章

      网友评论

          本文标题:vue日历组件

          本文链接:https://www.haomeiwen.com/subject/ksbjnltx.html