vue2 日历上通过鼠标按下滑动实现任意日期多选

vue2 日历上通过鼠标按下滑动实现任意日期多选

工作需求是实现一个在日历按下鼠标后随意移动鼠标实现多选,同时单击单选,网上搜了一圈都是点击两个日期然后这个范围全选,不能实现滑动鼠标多选,随意点击多选

效果图:

在这里插入图片描述

Ps:

从别人的代码改动而来的,找了一个个人看起相对接受度高的,改动之后实现了我的功能需求,原作者留下有一个多选方式不适合我的需求,但是相关功能代码我就懒得删除了,留放其中了,道友自取改动使用。子组件全部代码放在下边了,原文链接也在下方。

父组件代码:
<Calendar @date-selecteds = "handleDateSelected"  ref="childRef" ></Calendar>

两个事件,一个是把多选的值传给父组件,可以把子组件中的方法写这里,我是调试两下后懒得弄过来了,道友们就自己随心了。

//子组件日期列表返回值
    handleDateSelected(selectedDates) {
    this.form.listDate = selectedDates;
    },

一个是父组件的弹出框取消时清除下子组件中存储的多选值,放到你想调用清除的方法里。

 this.$refs.childRef.handleClear();
子组件代码:
<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 style="margin-left: 0px"></li>
        <li style="margin-left: -3px"></li>
        <li style="margin-left: -3px"></li>
        <li style="margin-left: -3px"></li>
        <li style="margin-left: -3px"></li>
        <li style="margin-left: -3px"></li>
        <li style="margin-left: -3px"></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,'user-select-none':true}"
            :key="index"
            @mousedown="startSelection(item)"
            @mouseover="handleMouseover(item)"
            @mouseup = "endSelection(item)">
          <span style="user-select: none;pointer-events: none">
             {{ item.day }}
          </span>
          <span v-if="item.isShowPoint" class="w-point" style="user-select: none;pointer-events: none"></span>
        </li>
      </ul>
    </div>

  </div>
</template>
<script>
import Da from 'element-ui/src/locale/lang/da'

export default {
  name: 'calender',
  props: {
    //是否是多选
    isMultipleChoice: {
      type: Boolean,
      default: false,
    },
    //是否显示清除按钮
    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: {},//如果是单选 则把选择的保存
      isStart : false,
    }
  },
  methods: {

    startSelection(item){
      //console.log("开始")
      this.isStart = true
      this.handleMouseover(item);
    },
    handleMouseover(item){
      if (this.isStart){
        //console.log(item.date)
        this.handleChooseDay(item);
      }
    },
    endSelection(item){
      //console.log("结束")
      this.isStart = false
      const map = {...this.chooseDateList}
      const dateList = Object.keys(map)
      //const dateList = Object.values(map)
      const datesArray = []
      for (let i = 0; i < dateList.length; i++) {
        datesArray[i] = new Date( parseInt(dateList[i], 10))
        //datesArray[i] = dateList[i].date
      }
      console.log(datesArray)
      this.$emit('date-selecteds', datesArray)
    },

    getCurDay(){
      var datetime = new Date();
      var year = datetime.getFullYear();
      var month = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1;
      var date = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate();
      return `${year}-${month}-${date}`
    },

    //得到当前年这个月分有多少天
    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 = this.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>
<style>
ul {
  list-style: none;
}

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

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

.w-top .w-top-date {
  white-space: nowrap;
  font-size: 18px;
  font-weight: bold;
}

.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: 5px 5px 5px 5px;
  font-size: 16px;
  box-sizing: border-box;
}

.w-date_wrap .w-week li {
  width: 14.28%;
  height: 10px;
  padding: 0 0 0 30px ;
}

.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;
}

.user-select-none {
  -webkit-user-select: none; /* Chrome, Safari, Opera */
  -moz-user-select: none; /* Firefox */
  -ms-user-select: none; /* Internet Explorer/Edge */
  user-select: none; /* Non-prefixed version, currently supported by all major browsers */
}

</style>

链接: 原文章地址

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值