时间:2022-07-08 10:29:29 | 栏目:vue | 点击:次
现在做的项目中遇到了左右滑动选择日期的一个功能,然后我封装了一下这个组件,现在分享给大家看一下:
效果图:
1、安装dayjs日期文件
npm install dayjs --save
2、封装的组件:
<template> <div class="m-calendar" ref="calendar"> <div class="m-toolbar"> <div class="m-year-selector"> <!-- <a class="m-prev-btn" @click="changeYear('prev')"></a> --> <span>{{showDate.year}}{{yearName}}</span> <!-- <a class="m-next-btn" @click="changeYear('next')"></a> --> </div> <div class="m-month-selector"> <!-- <a class="m-prev-btn" @click="changeMonth('prev')"></a> --> <span>{{monthNames[showDate.month-1]}}</span> <!-- <a class="m-next-btn" @click="changeMonth('next')"></a> --> </div> </div> <div class="m-week-header"> <div class="m-week-day" v-for="item in weekNames" :key="item" > {{item}} </div> </div> <div class="m-months-container" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend" > <div class="m-months-wrapper" :style="{'transform': `translate3d(${-translateX*100}%, 0, 0)`}" > <div class="m-months" v-for="(month,monthIndex) in fullDate" :key="monthIndex" :style="{ transform: `translate3d(${(monthIndex-1+translateX + (isTouching ? touch.x : 0))*100}%, 0, 0)`, transitionDuration: isTouching ? '0s' : '.3s', }" > <div class="m-row" v-for="(week,weekIndex) in month" :key="weekIndex" > <div class="m-day" v-for="(day,dayIndex) in week" :key="dayIndex" @click="onDayClick(day)" > <span :class="{ 'm-day-num':true, 'm-grey': day.isGrey, 'm-today': day.isToday, 'm-disable': day.isDisable, 'm-select': day.isSelect, }" > <!-- 'm-during': day.isDuring --> {{day.value}} </span> <slot name="day" :date="day" /> </div> </div> </div> </div> </div> </div> </template> <script> import dayjs from 'dayjs'; let touchStartPosition; let touchEndPosition; let timeStamp; export default { name: 'inlineCalendar', props: { defaultDate: { type: [Date, Number, Array, String, dayjs], }, disabledDate: { type: Array, default() { return []; }, }, minDate: { type: [Date, Number, Array, String, dayjs], }, maxDate: { type: [Date, Number, Array, String, dayjs], }, mode: { type: String, default: 'single', }, dayClick: { type: Function, default() { return function() { return true; }; }, }, enableTouch: { type: Boolean, default: true, }, monthNames: { type: Array, default() { return ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']; }, }, weekNames: { type: Array, default() { return ['一', '二', '三', '四', '五', '六', '日']; }, }, yearName: { type: String, default: '年', }, restrictCurrentMonth: { type: Boolean, default: false, }, }, watch: { mode() { this.init(); }, }, data() { return { fullDate: [[], [], []], translateX: 0, showDate: { year: undefined, month: undefined, }, dateNow: { year: dayjs().year(), month: dayjs().month() + 1, date: dayjs().date(), }, selectDate: [], touch: { x: 0, y: 0, }, isTouching: false, }; }, created() { this.init(); }, methods: { init(date) { this.selectDate = []; let { defaultDate, mode } = this; if (date) { defaultDate = date; } let dateToShow = dayjs().startOf('month'); if (mode === 'single' && defaultDate) { this.selectDate = dayjs(defaultDate).startOf('day'); dateToShow = this.selectDate.startOf('month'); } if (mode === 'multiple' && Array.isArray(defaultDate)) { if (defaultDate.length > 0) { this.selectDate = defaultDate.map((item) => dayjs(item).startOf('day')); } } if (mode === 'during' && Array.isArray(defaultDate)) { if (defaultDate.length === 2) { const startDate = dayjs(defaultDate[0]).startOf('day'); const endDate = dayjs(defaultDate[1]).startOf('day'); if (startDate.isBefore(endDate) || startDate.isSame(endDate)) { this.selectDate = [startDate, endDate]; } } } this.showDate = { year: dateToShow.year(), month: dateToShow.month() + 1, }; this.getFullDate(this.showDate); }, touchstart(event) { if (this.enableTouch) { touchStartPosition = event.touches[0].clientX; touchEndPosition = event.touches[0].clientY; timeStamp = event.timeStamp; this.touch = { x: 0, y: 0, }; this.isTouching = true; } }, touchmove(event) { if (this.enableTouch) { this.touch = { x: (event.touches[0].clientX - touchStartPosition) / this.$refs.calendar.offsetWidth, y: (event.touches[0].clientY - touchEndPosition) / this.$refs.calendar.offsetHeight, }; } }, touchend(event) { if (this.enableTouch) { this.isTouching = false; const during = dayjs(event.timeStamp).diff(timeStamp); if (Math.abs(this.touch.x) > Math.abs(this.touch.y) && Math.abs(this.touch.x * this.$refs.calendar.offsetWidth) > 20) { if (this.touch.x > 0) { this.changeMonth('prev'); } else if (this.touch.x < 0) { this.changeMonth('next'); } } else { this.touch = { x: 0, y: 0, }; } } }, // 触发change事件 emitChange() { this.$emit('change', this.selectDate); }, // 触发切换年月事件 emitSwitch(showDate) { if (this.restrictCurrentMonth) { this.selectDate = []; } this.$emit('switch', showDate); }, // 日期点击事件 onDayClick(day) { if (!this.dayClick(day.dateTime)) { return; } switch (this.$props.mode) { case 'single': if (!day.isSelect && !day.isDisable) { this.selectDate = day.dateTime; this.getFullDate(this.showDate); this.emitChange(); } break; case 'multiple': if (!day.isSelect && !day.isDisable) { this.selectDate.push(day.dateTime); this.getFullDate(this.showDate); this.emitChange(); } else { if (this.selectDate.length > 1) { this.selectDate = this.selectDate.filter((item) => !item.isSame(day.dateTime)); this.getFullDate(this.showDate); this.emitChange(); } } break; case 'during': if (day.isDisable) return; if (this.restrictCurrentMonth && day.isGrey) return; if (this.selectDate.length === 0) { this.selectDate = [day.dateTime]; } else if (this.selectDate.length === 1) { this.selectDate.push(day.dateTime); if (this.selectDate[1].isBefore(this.selectDate[0])) { this.selectDate.reverse(); } } else if (this.selectDate.length === 2) { this.selectDate = [day.dateTime]; } this.getFullDate(this.showDate); this.emitChange(); break; } }, // 切换年份 changeYear(action) { const date = dayjs(`${this.showDate.year}-${this.showDate.month}`); let computedDate; switch (action) { case 'prev': this.translateX += 1; computedDate = date.subtract(1, 'year'); break; case 'next': this.translateX -= 1; computedDate = date.add(1, 'year'); break; } this.showDate = { year: computedDate.year(), month: computedDate.month() + 1, }; this.emitSwitch(this.showDate); this.getFullDate(this.showDate); }, // 切换月份 changeMonth(action) { const date = dayjs(`${this.showDate.year}-${this.showDate.month}`); let computedDate; switch (action) { case 'prev': this.translateX += 1; computedDate = date.subtract(1, 'month'); break; case 'next': this.translateX -= 1; computedDate = date.add(1, 'month'); break; } this.showDate = { year: computedDate.year(), month: computedDate.month() + 1, }; this.emitSwitch(this.showDate); this.getFullDate(this.showDate); }, // 暴露出去的方法:切换已选的时间 changeDate(date) { if (dayjs(date).isValid() || Array.isArray(date)) { this.init(date); } else { console.error('Type of parameter is invalid!'); } }, // 暴露出去的方法:切换当前显示的时间 changeDateView(date = dayjs()) { const changeDate = dayjs(date); this.showDate = { year: changeDate.year(), month: changeDate.month() + 1, }; this.getFullDate(this.showDate); }, getFullDate() { const date = dayjs(`${this.showDate.year}-${this.showDate.month}`); const thisDate = this.getDate(date); const prevDate = this.getDate(date.subtract(1, 'month')); const nextDate = this.getDate(date.add(1, 'month')); this.fullDate = [ prevDate.fullDate, thisDate.fullDate, nextDate.fullDate, ]; }, // 当前日期是否被选中 isSelect(date) { // console.log(date) let select = false; switch (this.$props.mode) { case 'single': if (this.selectDate && date.isSame(this.selectDate)) { select = true; } break; case 'multiple': if (this.selectDate.length > 0 && this.selectDate.some((item) => date.isSame(item))) { select = true; } break; } return select; }, // 当前时间是否在selectDate之间 isBetting(date) { if (this.mode === 'during') { const startDate = this.selectDate[0]; const endDate = this.selectDate[1]; if (this.selectDate.length === 1) { return date.isSame(startDate); } else if (this.selectDate.length === 2) { return (date.isAfter(startDate) && date.isBefore(endDate)) || date.isSame(startDate) || date.isSame(endDate); } } return false; }, getIsDisable(dateTime) { let isDisable = false; const disabledDate = this.disabledDate.map((item) => dayjs(item).startOf('day')); if (this.minDate || this.maxDate) { if (this.minDate) { const minDate = dayjs(this.minDate).startOf('day'); isDisable = dateTime.isBefore(minDate); } if (!isDisable && this.maxDate) { const maxDate = dayjs(this.maxDate).endOf('day'); isDisable = dateTime.isAfter(maxDate); } } else if (disabledDate.length > 0) { if (this.mode !== 'during') { isDisable = disabledDate.some((item) => item.isSame(dateTime)); } } return isDisable; }, getDate(thisDate) { let date = []; const prevDate = thisDate.subtract(1, 'month'); const nextDate = thisDate.add(1, 'month'); const firstDayOfWeek = thisDate.day() || 7; const dayCountOfThisMonth = thisDate.daysInMonth(); const dayCountOfPrevMonth = prevDate.daysInMonth(); const prevIndexOfThisMonth = firstDayOfWeek - 1; const NextIndexOfThisMonth = firstDayOfWeek + dayCountOfThisMonth - 2; const disabledDate = this.disabledDate.map((item) => dayjs(item).startOf('day')); for (let i = 0; i < 7 * 6; i++) { // 上月 if (i < prevIndexOfThisMonth) { const value = dayCountOfPrevMonth - (firstDayOfWeek - i - 2); const dateTime = prevDate.date(value); date[i] = { value, dateTime, isGrey: true, isToday: dateTime.isSame(dayjs().startOf('day')), isSelect: this.isSelect(dateTime), isDisable: this.getIsDisable(dateTime), isDuring: this.isBetting(dateTime), }; } // 当月 if ( i >= prevIndexOfThisMonth && i <= NextIndexOfThisMonth ) { const value = i - firstDayOfWeek + 2; const dateTime = thisDate.date(value); date[i] = { value, dateTime, isGrey: false, isToday: dateTime.isSame(dayjs().startOf('day')), isSelect: this.isSelect(dateTime), isDisable: this.getIsDisable(dateTime), isDuring: this.isBetting(dateTime), }; } // 下月 if (i > NextIndexOfThisMonth) { const value = i - firstDayOfWeek - dayCountOfThisMonth + 2; const dateTime = nextDate.date(value); date[i] = { value, dateTime, isGrey: true, isToday: dateTime.isSame(dayjs().startOf('day')), isSelect: this.isSelect(dateTime), isDisable: this.getIsDisable(dateTime), isDuring: this.isBetting(dateTime), }; } } const fullDate = []; for (let i = 0; i < 6; i++) { fullDate.push(date.slice(i * 7, (i + 1) * 7)); } return { fullDate, }; }, }, }; </script> <style lang="less" scoped> @import './style.css'; </style>
相关的style.css文件
.m-calendar { background: #fff; box-shadow: 0px 2px 6px 0px rgba(183, 183, 183, 0.2); } .m-calendar .m-toolbar { padding-bottom: 0.36266667rem; } .m-calendar .m-toolbar { display: flex; height: 2.56rem; } .m-calendar .m-toolbar .m-month-selector, .m-calendar .m-toolbar .m-year-selector { display: flex; align-items: center; justify-content: space-between; padding-top: 0.74666667rem; } .m-calendar .m-toolbar .m-month-selector, .m-calendar .m-toolbar .m-year-selector { line-height: 1.06666667rem; } .m-calendar .m-toolbar .m-month-selector, .m-calendar .m-toolbar .m-year-selector { font-size: 0.768rem; font-family: PingFangSC-Medium, PingFangSC; font-weight: 500; color: #29262a; } .m-calendar .m-toolbar .m-year-selector { padding-left: 0.91733333rem; } .m-calendar .m-week-header { padding: 0 0.91733333rem; } .m-calendar .m-week-header { padding-bottom: 0.512rem; } .m-calendar .m-week-header { position: relative; display: flex; box-sizing: border-box; justify-content: space-between; font-size: 0.59733333rem; } .m-calendar .m-week-header .m-week-day { text-align: left; line-height: 0.85333333rem; font-family: PingFangSC-Regular, PingFangSC; font-weight: 400; color: #222222; } .m-calendar .m-months-container { position: relative; box-sizing: border-box; height: 12.37333333rem; overflow: hidden; } .m-calendar .m-months-container .m-months-wrapper { position: absolute; top: 0; left: 0; right: 0; bottom: 0; } .m-calendar .m-months-container .m-months-wrapper .m-months { position: absolute; top: 0; left: 0; right: 0; bottom: 0; will-change: transform; width: 16rem; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row { padding-top: 0.512rem; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row { width: 16rem; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row { position: relative; display: flex; height: 1.408rem; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day { margin-right: 0.87466667rem; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day:nth-child(1) { margin-left: 0.66133333rem; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day { font-size: 0.59733333rem; font-family: PingFangSC-Medium, PingFangSC; font-weight: 500; color: #222222; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day { position: relative; height: 1.408rem; width: 1.408rem; line-height: 1.408rem; text-align: center; cursor: pointer; -webkit-tap-highlight-color: transparent; border-radius: 50%; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day .m-day-num { width: 1.408rem; display: inline-block; border-radius: 100%; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day .m-day-num { height: 1.408rem; line-height: 1.408rem; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day .m-grey { color: #b8b8b8; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day .m-today { background: #5DABF3; color: #fff; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day .m-disable { color: #b8b8b8; text-decoration: line-through; } .m-calendar .m-months-container .m-months-wrapper .m-months .m-row .m-day .m-select { background: #007aff; color: #fff; }
3、使用到的页面
<div class="data"> <inlineCalendar :dayClick="dayClick" /> </div>
<script> import inlineCalendar from '../components/inlineCalendar'; export default { name: "home", data() { return { }; }, components: { inlineCalendar }, methods: { dayClick(date) { console.log('date---->', date); console.log(date.format('YYYY-MM-DD')); let dates = date.format('YYYY-MM-DD'); }, } }; </script> <style lang="less" scoped> .data { position: fixed; top: 1.87733333rem; width: 100%; height: 100%; } </style>