当前位置: 首页 > 滚动 > >正文

【世界新视野】vue平铺日历组件之按住ctrl、shift键实现跨月、跨年多选日期的功能

来源:博客园    时间:2023-05-24 15:46:04


(资料图片仅供参考)

已经好久没有更新过博客了,大概有两三年了吧,因为换了工作,工作也比较忙,所以也就没有时间来写技术博客,期间也一直想写,但自己又比较懒,就给耽误了。今天这篇先续上,下一篇什么时候写,我也不知道,随心所欲、随遇而安、安之若素、素不相识也就无所谓了吧。

一开始看到这个功能需求,我也很懵逼,因为从来没有做过啊,哈哈。。。但转念一想既然产品能提出这个需求,想必会有人实现过,就去网上查了查资料,果不其然,还真有人做过,但离我想要的效果还是差着十万八千里,所以按照网上大神的思路,结合我司的实际需求,自己就把它给捣鼓出来了。

其实刚做好的效果还是能实现产品的需求的,我就让同事帮忙测试一下看看有没有什么bug,因为我司对bug的要求以及对用户体验的要求还是很严格的,所以在同事的实际测试以及提醒下,后面我又参照电脑上基于ctrl、shift键来选中文件的实际效果做了改进,算是不给测试提bug的机会吧。

照旧先上两张最后实现的效果图:

先来看看实现单月的日期组件calendar.vue

<script>import dayjs from "dayjs"import { mapState, mapMutations } from "vuex"const monthCN = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"]const getNewDate = date => {  const year = date.getFullYear()  const month = date.getMonth()  const day = date.getDate()  return { year, month, day }}const getDate = (year, month, day) => new Date(year, month, day)export default {  props: {    year: {      type: [String, Number],      default: 2023    },    month: {      type: [String, Number],      default: 0    },    // 是否在当月日历中展示上个月和下个月的部分日期,默认不展示。    hasCurrentMonth: {      type: Boolean,      default: false    },    // 休息日    weekendList: Array,    // 工作日    workList: {      type: Array,      default: () => []    },    // 既非工作日也非休息日    workRestOffList: Array  },  data () {    return {      calendarWeek: ["一", "二", "三", "四", "五", "六", "日"],      today: dayjs().format("YYYYMMDD"),      isCtrl: false,      isShift: false,      monthCN    }  },  computed: {    calendar () {      const calendatArr = []      const { year, month } = getNewDate(getDate(this.year, this.month, 1))      const currentFirstDay = getDate(year, month, 1)      // 获取当前月第一天星期几      const weekDay = currentFirstDay.getDay()      let startTime = null      if (weekDay === 0) {        // 当月第一天是星期天        startTime = currentFirstDay - 6 * 24 * 60 * 60 * 1000      } else {        startTime = currentFirstDay - (weekDay - 1) * 24 * 60 * 60 * 1000      }      // 为了页面整齐排列 一并绘制42天      for (let i = 0; i < 42; i++) {        calendatArr.push({          date: new Date(startTime + i * 24 * 60 * 60 * 1000),          year: new Date(startTime + i * 24 * 60 * 60 * 1000).getFullYear(),          month: new Date(startTime + i * 24 * 60 * 60 * 1000).getMonth() + 1,          day: new Date(startTime + i * 24 * 60 * 60 * 1000).getDate()        })      }      return calendatArr    },    ...mapState("d2admin", { selectedList: s => s.multiMarketCalendar.selectList })  },  mounted () {    this.onKeyEvent()  },  methods: {    ...mapMutations({ setSelectCalendar: "d2admin/multiMarketCalendar/setSelectCalendar" }),    onKeyEvent () {      window.addEventListener("keydown", e => {        const _e = e || window.event        switch (_e.keyCode) {          case 16:            this.isShift = true            break          case 17:            this.isCtrl = true            break        }      })      window.addEventListener("keyup", e => {        const _e = e || window.event        switch (_e.keyCode) {          case 16:            this.isShift = false            break          case 17:            this.isCtrl = false            break        }      })    },    // 是否是当前月    isCurrentMonth (date) {      const { year: currentYear, month: currentMonth } = getNewDate(getDate(this.year, this.month, 1))      const { year, month } = getNewDate(date)      return currentYear === year && currentMonth === month    },    onDay (item) {      // 如果是同一年,当点击的月份小于当前系统日期的月份,则禁止点击。      if (item.year === new Date().getFullYear() && item.month < new Date().getMonth() + 1) {        return      }      // 如果是同一年又是同一个月,当点击的日期小于当前的日期,则禁止点击      if (item.year === new Date().getFullYear() && item.month === new Date().getMonth() + 1 && item.day < new Date().getDate()) {        return      }      // 如果点击的年份小月当前年份,则禁止点击。      if (item.year < new Date().getFullYear()) {        return      }      // 如果点击的日期不是那个月应有的日期(每个月的日期里边也会包含上个月和下个月的某几天日期,只是这些日期的颜色被置灰了),则禁止点击。      if (!this.isCurrentMonth(item.date)) {        return      }      const dateStr = dayjs(item.date).format("YYYYMMDD")      const { isCtrl, isShift } = this      // 按住ctrl      if (isCtrl && !isShift) {        this.setSelectCalendar({ dateStr, item, isCtrl })      } else if (isCtrl && isShift) { // 同时按住ctrl和shift        this.setSelectCalendar({ dateStr, item, isCtrl, isShift })      } else if (isShift && !isCtrl) { // 按住shift        this.setSelectCalendar({ dateStr, item, isShift })      } else { // 不按ctrl和shift,一次只能选择一个        this.setSelectCalendar({ dateStr, item })      }    },    getWeekClass (year, month) {      if (year < new Date().getFullYear()) return "is-disabled"      if (year === new Date().getFullYear() && month < new Date().getMonth() + 1) return "is-disabled"    },    getCellClass (item, idx) {      const { workList, weekendList, workRestOffList } = this      const date = dayjs(item.date).format("YYYYMMDD")      const today = `${item.year}${item.month < 10 ? "0" + item.month : item.month}${item.day < 10 ? "0" + item.day : item.day}`      // 被选中的日期      const index = this.selectedList.indexOf(date)      if (index !== -1) {        // 如果选中的日期是当年日期的周六周日,且这些日期不是工作日,则在选中时还是要保留它原来的红色字体,如果是工作日,则在选中时就不能再展示成红色字体了。        if ((idx % 7 === 5 || idx % 7 === 6) && this.isCurrentMonth(item.date) && item.year >= new Date().getFullYear() && !(workList.length && workList.includes(date))) {          return "is-selected is-weekend"        } else if (weekendList.length && weekendList.includes(date)) { // 休息日的选中(非周六周日的休息日)          return "is-selected is-weekend"        } else if (workRestOffList.length && workRestOffList.includes(date)) { // 异常日期的选中          return "is-selected is-workRestOff"        } else {          return "is-selected"        }      }      // 默认显示当前日期      if (today === this.today) {        // 如果把当前日期也置为休息日,则也是要将当前日期标红        if (weekendList.length && weekendList.includes(date)) {          return "is-today is-weekend"        }        // 异常日期        if (workRestOffList.length && workRestOffList.includes(date)) {          return "is-today is-workRestOff"        }        return "is-today"      }      // 如果日期不是那个月应有的日期(每个月的日期里边也会包含上个月和下个月的某几天日期,只是这些日期的颜色被置灰了),则禁止点击。      // if (!this.isCurrentMonth(item.date)) {      //   return "is-disabled"      // }      // 如果是同一年,当月份小于当前系统日期的月份,则禁止点击。      if (item.year === new Date().getFullYear() && item.month < new Date().getMonth() + 1) {        // 周六周日被设置成了工作日        if ((idx % 7 === 5 || idx % 7 === 6) && (workList.length && workList.includes(date))) {          return "is-disabled is-work"        }        // 异常日期        if (workRestOffList.length && workRestOffList.includes(date)) {          return "is-disabled is-workRestOff"        }        // 周六周日标红        if (idx % 7 === 5 || idx % 7 === 6 || (weekendList.length && weekendList.includes(date))) {          return "is-disabled is-weekend"        }        return "is-disabled"      }      // 如果是同一年又是同一个月,当日期小于当前的日期,则禁止点击      if (item.year === new Date().getFullYear() && item.month === new Date().getMonth() + 1 && item.day < new Date().getDate()) {        // 周六周日被设置成了工作日        if ((idx % 7 === 5 || idx % 7 === 6) && (workList.length && workList.includes(date))) {          return "is-disabled is-work"        }        // 异常日期        if (workRestOffList.length && workRestOffList.includes(date)) {          return "is-disabled is-workRestOff"        }        // 周六周日标红        if (idx % 7 === 5 || idx % 7 === 6 || (weekendList.length && weekendList.includes(date))) {          return "is-disabled is-weekend"        }        return "is-disabled"      }      // 如果年份小月当前年份,则禁止点击。      if (item.year < new Date().getFullYear()) {        // 周六周日被设置成了工作日        if ((idx % 7 === 5 || idx % 7 === 6) && (workList.length && workList.includes(date))) {          return "is-disabled is-work"        }        // 异常日期        if (workRestOffList.length && workRestOffList.includes(date)) {          return "is-disabled is-workRestOff"        }        // 周六周日标红        if (idx % 7 === 5 || idx % 7 === 6 || (weekendList.length && weekendList.includes(date))) {          return "is-disabled is-weekend"        }        return "is-disabled"      }      // 周六周日标红      if ((idx % 7 === 5 || idx % 7 === 6) && this.isCurrentMonth(item.date) && item.year >= new Date().getFullYear()) {        // 周六周日被设置成了工作日,则不标红        if (workList.length && workList.includes(date)) {          return "is-work"        }        if (workRestOffList.length && workRestOffList.includes(date)) {          return "is-workRestOff"        }        return "is-weekend"      }      // 休息日标红      if (weekendList.length && weekendList.includes(date)) {        return "is-weekend"      }      // 既非工作日也非休息日      if (workRestOffList.length && workRestOffList.includes(date)) {        return "is-workRestOff"      }    }  },  beforeDestroy() {    window.removeEventListener("keydown")    window.removeEventListener("keyup")  }}</script>

从以上也能看出,我们的需求还是很复杂的,比如,历史日期也就是所谓的过期的日期不能被选中,周六周日的日期要标红,还可以把工作日置为休息日,休息日置为工作日等等。这些功能说起来就一句话,可要实现出来,那可就属实太复杂了。

对了,在实现的过程中,我还使用到了vuex来保存点击选中的数据calendar.js:

let selectList = []let shiftData = nulllet shiftDate = ""let currentSelect = []let lastSelect = []// 获取两个日期中间的所有日期const getBetweenDay = (starDay, endDay) => {  var arr = []  var dates = []  // 设置两个日期UTC时间  var db = new Date(starDay)  var de = new Date(endDay)  // 获取两个日期GTM时间  var s = db.getTime() - 24 * 60 * 60 * 1000  var d = de.getTime() - 24 * 60 * 60 * 1000  // 获取到两个日期之间的每一天的毫秒数  for (var i = s; i <= d;) {    i = i + 24 * 60 * 60 * 1000    arr.push(parseInt(i))  }  // 获取每一天的时间  YY-MM-DD  for (var j in arr) {    var time = new Date(arr[j])    var year = time.getFullYear(time)    var mouth = (time.getMonth() + 1) >= 10 ? (time.getMonth() + 1) : ("0" + (time.getMonth() + 1))    var day = time.getDate() >= 10 ? time.getDate() : ("0" + time.getDate())    var YYMMDD = year + "" + mouth + "" + day    dates.push(YYMMDD)  }  return dates}const shiftSelect = (dateStr, item) => {  if (!shiftData) {    shiftData = item.date    shiftDate = `${item.year}-${item.month}-${item.day}`    // 如果当前日期已选中,再次点击当前日期就取消选中。    if (selectList.includes(dateStr)) {      selectList.splice(selectList.indexOf(dateStr), 1)    } else {      selectList.push(dateStr)    }  } else {    if (shiftData < item.date) {      currentSelect = getBetweenDay(shiftDate, `${item.year}-${item.month}-${item.day}`)    } else if (shiftData > item.date) {      currentSelect = getBetweenDay(`${item.year}-${item.month}-${item.day}`, shiftDate)    } else {      currentSelect = [dateStr]    }    selectList = selectList.filter(item => !lastSelect.includes(item)) // 移除上次按shift复制的    selectList = selectList.concat(currentSelect) // 添加本次按shift复制的    lastSelect = currentSelect  }}export default {  namespaced: true,  state: {    selectList: []  },  mutations: {    setSelectCalendar (s, { dateStr, item, isCtrl, isShift }) {      // 按住ctrl      if (isCtrl && !isShift) {        // 如果当前日期已选中,再次点击当前日期就取消选中。        if (selectList.includes(dateStr)) {          selectList.splice(selectList.indexOf(dateStr), 1)        } else {          selectList.push(dateStr)        }      // 加上lastSelect = []这个就会在shift连续选了多个后,再按住ctrl键取消已选中的日期的中间的几个后,下次再按住      // shift多选时,就会从按住ctrl取消选中的最后一个日期开始连续选择,但刚才取消已选中的那些日期      // 之前的已经选中的日期依旧处于选中状态,与电脑自身的按住shift键多选的效果略有不同,所以要注释掉这串代码。      // lastSelect = []  // 最开始这串代码是放开的      } else if (isShift && !isCtrl) {        // 加上selectList = []可以实现按住ctrl单选几个不连续的日期后,再按住shift键连选其他日期,就可以把之前        // 按住ctrl键单选的其他日期都取消选中。不加selectList = []是可以在按住shift键连选其他日期时同时保留之前        // 按住ctrl键单选的其他日期。        selectList = [] // 最开始这串代码是没有的        shiftSelect(dateStr, item)      } else if (isCtrl && isShift) { // 同时按住ctrl和shift        lastSelect = []        shiftSelect(dateStr, item)      } else { // 不按ctrl和shift,一次只能选择一个        selectList = [dateStr]      }      if (!isShift) {        shiftData = item.date        shiftDate = `${item.year}-${item.month}-${item.day}`      }      selectList = [...new Set(selectList)].sort() // 去重、排序      s.selectList = selectList    },    setSelectEmpty (s) {      selectList = []      shiftData = null      shiftDate = ""      currentSelect = []      lastSelect = []      s.selectList = []    }  }}

再来看看在以上单个日期组件的基础上实现1年12个月日历平铺的代码吧:

<script>import Calendar from "./calendar"export default {  components: { Calendar },  data(){    return {      year: new Date(),      workList: ["20220206", "20230107", "20230311"],      weekendList: [{date: "20220118", market: "马来西亚,泰国"}, {date: "20230123", market: "中国澳门,韩国"}, {date: "20230213", market: "中国香港,美国"}, {date: "20230313", market: "中国台湾,法国"}],      workRestOffList: ["20220101", "20220105", "20230101", "20230105", "20230218", "20230322", "20230330"],    }  },  methods: {    onChange(v){      this.year = v    }  }}</script>

写到这里就不准备再往下写了,所有的代码都贴出来了。如果你想要的效果跟我的这个不太一样,你自己在这个基础上改吧改吧应该就可以用了。其实最关键的部分也就是那个按住ctrl、shift键实现跨月、跨年多选日期,理解了这个原理,其余的实现部分尽管复杂但并不难。

X 关闭

推荐内容

最近更新

Copyright ©  2015-2022 海峡医疗网版权所有  备案号:皖ICP备2022009963号-10   联系邮箱:396 029 142 @qq.com