自己捣鼓日历选择组件(类似el-calendar)可以单击选则不同日期,也可范围选择日期

需求:

  • 左边日历,选择日历中的日期,右边把选择的日期显示到表格中去
  • 每个日期显示一行数据,左侧日期点击选中再次点击取消,取消右边表格数据也跟着取消
  • 日历中的日期可以进行多个选则,这里不进行范围选则而是进行跨天和跨月进行选择

分析:

  1. 日历好像element-ui库中有el-calendar,赶紧看一下有没有这样的类似的事件,看了文档发现,只有单点选择,且选中后不能取消
  2. 想了想看看有没有类似的库,发现有,但是项目中已经够大了再加一些第三方库不太好,于是考虑自己封装一个日历组件,可单选取消,间隔多选,范围选择,这个通过配置就可以,方便以后使用

效果如下

1、范围选择

 2、单点选择

 3、插槽添加选择月份

4、禁止选择指定日期以前的时间 :restrictDate="{1:['2022-02-11']}"

 5、禁止选择以后的日期 :restrictDate="{2:['2022-02-11']}"

6、 禁止选择某个范围的日期 :restrictDate="{3:['2022-02-11','2022-03-11']}"

7、给日期加上标记点 pointMap: {[new Date('20220-09-01').getTime()]: true},

 

由于时间充足,就把该组件打包发布到了npm上了,使用方式如下

  1.  npm i wqcalendar -S  包使用教程点击这里
  2. 源码已发布GitHub

html代码

<template>
  <div class="w-calender-container">
    <div class="w-top">
      <div class="w-chooseMonth">
        <slot name="left"></slot>
      </div>
      <div class="w-top-date">
        {{ year }}年{{ `${month > 9 ? month : '0' + month}` }}月
      </div>
      <div class="w-btn-wrap">
        <span @click="handleShowLastMonth">上一月</span>
        <span @click="handleShowToday">今天</span>
        <span @click="handleShowNextMonth">下一月</span>
        <span v-if="isShowClear" @click="handleClear">清除</span>
      </div>
    </div>
    <div class="w-date_wrap">
      <ul class="w-week">
        <li>日</li>
        <li>一</li>
        <li>二</li>
        <li>三</li>
        <li>四</li>
        <li>五</li>
        <li>六</li>
      </ul>
      <ul class="w-day">
        <li v-for="(item,index) in days"
            :class="{'c-isCurMonth':item.isNextMonth||item.isLastMonth,'c-isCurToday':item.isCurToday,'c-isActive':item.isActive,'c-is-previous-date':item.isPreviousDate}"
            :key="index"
            @click="handleChooseDay(item)">
          <span>
             {{ item.day }}
          </span>
          <span v-if="item.isShowPoint" class="w-point"></span>
        </li>
      </ul>
    </div>
    <div class="date-tip">
      <div class="tip_row">
        <span class="square"></span>
        <span class="title">可选</span>
      </div>
      <div class="tip_row">
        <span class="square chosen"></span>
        <span class="title">已选中</span>
      </div>
    </div>
  </div>
</template>

js代码

工具类点击这个 import MyTools from"../utils/MyTools";

<script>


import {getCurDay} from "../utils/MyTools";

export default {
  name: 'calender',
  props: {
    //是否是多选
    isMultipleChoice: {
      type: Boolean,
      default: true,
    },
    //是否显示清除按钮
    isShowClear: {
      type: Boolean,
      default: true
    },
    //选择的月份
    Month: {
      type: String,
    },
    //需要加圆点的数据
    //数据格式为map { new Date(2022-02-11).getTime():true}: 用年月日的时间戳为key
    pointMap:{
      type:Object,
      default:()=>{
        return {}
      }
    },
    //是否禁止选择以前的日期 默认为空表示没有限制 注:如果只有数字则 默认时间是当天时间
    //{1:['2022-02-11']}表示2022-02-11以前的日期不能选择;1:表示小于该时间的禁止选择,必填
    //{2:['2022-02-11']}表示2022-02-11以后的日期不能选择;2:表示大于该时间的禁止选择,必填
    //{3:['2022-02-11','2022-03-11']}表示2022-02-11到2022-03-11之间的日期可以选择;3:表示两个时间内的时间可以选择,必填
    //{4:['2022-02-11','2022-03-11']}表示2022-02-11到2022-03-11之外的日期可以选择;4:表示两个时间之外的可以选择,必填
    restrictDate: {
      type: Object,
      default: () => {
        return null
      }
    }
  },
  watch: {
    Month: {
      handler(val) {
        if (val) {
          const date = val.split('-').map(Number);
          this.year = date[0];
          this.month = date[1]
          this.days = [];
          //选择月份后重新跟更新时间
          this.dealDate();
        }
      },
      immediate: true
    },
    pointMap: {
      handler(val) {
        if (val) {
          this.dealDate();
        }
      },
      immediate: true
    }
  },
  data() {
    return {
      year: '',//年
      month: '',//月
      days: [],//日期
      //是否已经选择了开始时间
      isChooseOne: false,
      endTime: null,
      startTime: null,
      monthValue: '',//选择的月份
      chooseDateList: {},//如果是单选 则把选择的保存
    }
  },
  methods: {
    //得到当前年这个月分有多少天
    getDays(Y, M) {
      return new Date(Y, M, 0).getDate();
    },
    //得到当前年,这个月的一号是周几
    getWeek(Y, M) {
      let now = new Date()
      now.setFullYear(this.year)
      now.setMonth(this.month - 1)
      now.setDate(1);
      return now.getDay();
    },
    /**
     * 获取本月日期
     */
    pushDays() {
      //将这个月多少天加入数组days
      const m = `${this.month > 9 ? this.month : '0' + this.month}`;
      for (let i = 1; i <= this.getDays(this.year, this.month); i++) {
        const d = `${i > 9 ? i : '0' + i}`,
            date = `${this.year}-${m}-${d}`;
        this.days.push({
          day: d,
          isActive: false,
          month: m,
          year: `${this.year}`,
          date,
          timestamp: new Date(date).getTime(),//转换时间戳
        })
      }
      //获取上个月的日期
      this.getLastMonthDays()
      //获取下个月的日期
      this.getNextMonthDays()
    },
    /**
     * 获取下个月的日期
     */
    getNextMonthDays() {
      const m = this.month < 12 ? this.month + 1 : 1,
          y = this.month < 12 ? this.year : this.year + 1,
          len = 42 - this.getDays(this.year, this.month) - this.getWeek(this.year, this.month),
          _m = `${m > 9 ? m : '0' + m}`;
      //将下个月要显示的天数加入days
      for (let i = 1; i <= len; i++) {
        const _d = `${i > 9 ? i : '0' + i}`,
            date = `${y}-${_m}-${_d}`
        this.days.push({
          day: _d,
          month: _m,
          year: `${y}`,
          isActive: false,
          isNextMonth: true,
          date,
          timestamp: new Date(date).getTime()
        })
      }
    },
   /**
     * 获取上个月的日期
     */
    getLastMonthDays() {
      const m = this.month > 1 ? this.month - 1 : this.year > 1970 ? 12 : 1,
          y = this.month > 1 ? this.year : this.year > 1970 ? this.year - 1 : 1970,
          len = this.getWeek(this.year, this.month),
          lastMonthDays = this.getDays(this.year, this.month - 1)
      //将上个月要显示的天数加入days
      for (let i = 0; i < len; i++) {
        const _m = `${m > 9 ? m : '0' + m}`,
            date = `${y}-${_m}-${lastMonthDays - i}`
        this.days.unshift({
          day: `${lastMonthDays - i}`,
          month: _m,
          year: `${y}`,
          isActive: false,
          isLastMonth: true,
          date,
          timestamp: new Date(date).getTime(),
        })
      }
    },
    /**
     * 上个月
     */
    handleShowLastMonth() {
      if (this.month > 1) {
        this.month = this.month - 1;
      } else if (this.year > 1970) {
        this.month = 12;
        this.year = this.year - 1;
      }
      this.dealDate();
    },
    /**
     * 下个月
     */
    handleShowNextMonth() {
      this.days = [];
      if (this.month < 12) {
        this.month = this.month + 1;
      } else {
        this.month = this.month = 1;
        this.year = this.year + 1;
      }
      this.dealDate();
    },
    /**
     * 当天
     */
    handleShowToday() {
      let now = new Date();
      this.year = now.getFullYear();
      this.month = now.getMonth() + 1;
      this.dealDate()
    },
 /**
     * 处理时间
     */
    dealDate() {
      this.days = [];
      const curDate = getCurDay()
      this.pushDays();
      // 判断 是否需要禁止选择某些时间段的时间
      if (this.restrictDate) {
        const keys = Object.keys(this.restrictDate);
        let day, timestamp;
        switch (keys[0]) {
          case '1':
            day = this.restrictDate[keys[0]] && this.restrictDate[keys[0]] || curDate;
            timestamp = new Date(day).getTime();//转换时间戳
            this.days.forEach(item => {
              item.isCurToday = item.date === curDate
              item.isPreviousDate = item.timestamp < timestamp
            })
            break;
          case '2':
            day = this.restrictDate[keys[0]] && this.restrictDate[keys[0]] || curDate;
            timestamp = new Date(day).getTime();//转换时间戳
            this.days.forEach(item => {
              item.isCurToday = item.date === curDate
              item.isPreviousDate = item.timestamp > timestamp
            })
            break;
          case '3':
            const s_d = this.restrictDate[keys[0]] && this.restrictDate[keys[0]][1] || curDate
            const e_d = this.restrictDate[keys[0]] && this.restrictDate[keys[0]][2] || curDate
            let s = new Date(s_d).getTime(),
                e = new Date(e_d).getTime();
            if (s > e) {
              [s, e] = [e, s]
            }
            this.days.forEach(item => {
              item.isCurToday = item.date === curDate
              item.isPreviousDate = item.timestamp > s && item.timestamp < e
            })
            break
          case '4':
            const st_d = this.restrictDate[keys[0]] && this.restrictDate[keys[0]][1] || curDate
            const en_d = this.restrictDate[keys[0]] && this.restrictDate[keys[0]][2] || curDate
            let st = new Date(st_d).getTime(),
                en = new Date(en_d).getTime();
            if (st > en) {
              [st, en] = [en, st]
            }
            this.days.forEach(item => {
              item.isCurToday = item.date === curDate
              item.isPreviousDate = item.timestamp < st || item.timestamp > en
            })
            break;
          default:
            this.days.forEach(item => {
              item.isCurToday = item.date === curDate
            })
        }
      } else {
        this.days.forEach(item => {
          item.isCurToday = item.date === curDate
        })
      }
      this.getActiveDay()
    },
    /**
     * 清空选择
     */
    handleClear() {
      this.isChooseOne = false;
      this.startTime = null;
      this.endTime = null;
      this.chooseDateList = {};
      this.days.forEach(item => {
        item.isActive = false
      })
    },
   
         /**
     * 选择时间
     * @param time
     */
    handleChooseDay(time = {}) {
      // 判断 是否是禁止选择的日期 是否是
      if (this.restrictDate && time.isPreviousDate) {
        return
      }
      //是否是多选
      if (this.isMultipleChoice) {
        this.chooseDateList = {}
        //选择开始时间-结束时间
        if (this.isChooseOne) {
          this.endTime = time;
          const {timestamp} = this.startTime || {};
          //如果 选择的开始日期大于结束日期 则调换开始日期与结束日期
          if (timestamp > time.timestamp) {
            [this.startTime, this.endTime] = [this.endTime, this.startTime]
          }
          this.getActiveDay();
          this.isChooseOne = false
          this.$emit('chooseDays', this.chooseDateList)
        } else {
          this.isChooseOne = true
          this.startTime = time;
          //给选择的时间范围选中
          this.days.forEach(item => {
            item.isActive = item.timestamp === this.startTime.timestamp;
          })
        }
      } else {
        this.days.forEach(item => {
          if (item.timestamp === time.timestamp) {
            item.isActive = !time.isActive;
            if (time.isActive) {
              const {date, day, month, timestamp, year} = item
              this.chooseDateList[time.timestamp] = {
                date,
                day,
                month,
                timestamp,
                year
              }
            } else {
              delete this.chooseDateList[time.timestamp]
            }
          }
        })

        this.$emit('chooseDays', this.chooseDateList)
      }

    },
    /**
     * 给选择的日期范围加上选中状态
     */
    getActiveDay() {
      if (this.isMultipleChoice) {
        if (!this.startTime || !this.endTime) {
          return
        }
        //给选择的时间范围选中
        const {timestamp} = this.startTime || {};
        const {timestamp: endTimestamp} = this.endTime || {};
        this.days.forEach(item => {
          item.isActive = item.timestamp >= timestamp && item.timestamp <= endTimestamp && !item.isPreviousDate;
          //是否显示点
          item.isShowPoint=this.pointMap[item.timestamp]
          if (item.timestamp >= timestamp && item.timestamp <= endTimestamp && !item.isPreviousDate) {
            const {date, day, month, timestamp, year} = item
            this.chooseDateList[item.timestamp] = {
              date,
              day,
              month,
              timestamp,
              year
            }
          } else {
            delete this.chooseDateList[item.timestamp];
          }
        })
      } else {
        this.days.forEach(item => {
          //是否显示点
          item.isShowPoint=this.pointMap[item.timestamp]
          //已选择的数据加上状态
          item.isActive=!!this.chooseDateList[item.timestamp]
        })
      }
    }
  },
  mounted() {
    this.handleShowToday()
  }
}
</script>

css 代码

<style>
ul {
  list-style: none;
}

.w-calender-container {
  width: 100%;
  min-width: 400px;
  border: 1px solid #ddd;
  padding: 20px;
  box-sizing: border-box;
}

.w-top {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1% 0;
  border-bottom: 1px solid #ddd;
  box-sizing: border-box;
}

.w-top .w-top-date {
  white-space: nowrap;
}

.w-top .w-btn-wrap {
  min-width: 200px;
  display: flex;
  justify-content: space-around;
  color: #409EFF;
}

.w-btn-wrap span {
  flex: 1;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 16px;
}

.w-btn-wrap span:last-child {
  color: #FF6200;
}

.w-date_wrap {
  width: 100%;
  height: auto;
}

.w-date_wrap .w-week {
  width: 100%;
  display: flex;
  flex-direction: row;
  padding: 10px 0 10px 40px;
  font-size: 16px;
  box-sizing: border-box;
}

.w-date_wrap .w-week li {
  width: 14.28%;
}

.w-date_wrap .w-day {
  width: 100%;
  display: flex;
  flex-direction: row;
  padding: 0 20px;
  font-size: 16px;
  flex-wrap: wrap;
  box-sizing: border-box;
}

.w-day li {
  position: relative;
  cursor: pointer;
  width: 14.28%;
  padding: 4%;
  border: 1px solid #ddd;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
}

.w-day li .w-point {
  position: absolute;
  bottom: 26%;
  background: #ff0000;
  display: flex;
  width: 4px;
  height: 4px;
  border-radius: 50%;
}

.w-day li:nth-child(n+8) {
  border-top: none;
}

.w-day li:nth-child(n+1) {
  border-right: none;
}

.w-day li:nth-child(7n) {
  border-right: 1px solid #ddd
}

.date-tip {
  height: 40px;
  width: 100%;
  display: flex;
}

.tip_row {
  width: 100px;
  height: 100%;
  display: flex;
  align-items: center;
}

.tip_row:first-child {
  padding-left: 20px;
  box-sizing: border-box;
}

.tip_row .square {
  width: 20px;
  height: 20px;
  display: flex;
  background: #fff;
  border: 1px solid #c0c4cc;
}

.tip_row .chosen {
  background: #409EFF;
  border: 0;
}

.tip_row .title {
  display: flex;
  padding-left: 6px;
  box-sizing: border-box;
  white-space: nowrap;
}


.c-isCurMonth {
  background: #fff;
  color: #c0c4cc;
}

.c-isCurToday {
  background: #fff;
  color: #409EFF;
}


.c-isActive {
  background: #409EFF;
  color: #f2f8fe;
}

.chooseMonth {
  width: 120px;

}

.isCurYearDay {
  cursor: not-allowed !important;
  opacity: 0.6;
}

.c-isCurMonth.c-isActive {
  background: rgba(64, 158, 255, 0.56);
}

.c-is-previous-date {
  background: rgba(192, 196, 204, 0.2);
  color: #c0c4cc;
  cursor: not-allowed !important;
}

</style>

  • 16
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值