Last active
August 23, 2017 09:00
-
-
Save sliwey-zz/a9303e0fc66a5acec2322b0fdfdaa416 to your computer and use it in GitHub Desktop.
data-picker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <template> | |
| <div class="calendar-wrap" :class="size" v-clickOutside="handleClose"> | |
| <input | |
| type="text" | |
| readonly="readonly" | |
| class="calendar-input" | |
| :value="selectDateString" | |
| @click="handleToggle"> | |
| <transition name="slide-down" @after-leave="handleAfterLeave"> | |
| <div class="calendar-drop" :style="dropStyle" v-show="visible"> | |
| <div class="calendar-head"> | |
| <span class="prevYear" @click="handleAdd(currentView === 'year' ? -10 : -1, 'years')"></span> | |
| <span class="prevMonth" @click="handleAdd(-1, 'months')" v-show="currentView === 'date'"></span> | |
| <span class="calendar-year" @click="currentView = 'year'">{{ yearString }}</span> | |
| <span class="calendar-month" v-show="currentView === 'date'" @click="currentView = 'month'">{{ currDate.format('M') }}月</span> | |
| <span class="nextYear" @click="handleAdd(currentView === 'year' ? 10 : 1, 'years')"></span> | |
| <span class="nextMonth" @click="handleAdd(1, 'months')" v-show="currentView === 'date'"></span> | |
| </div> | |
| <ul class="calendar-list" v-show="currentView === 'date'"> | |
| <li class="calendar-item week">日</li> | |
| <li class="calendar-item week">一</li> | |
| <li class="calendar-item week">二</li> | |
| <li class="calendar-item week">三</li> | |
| <li class="calendar-item week">四</li> | |
| <li class="calendar-item week">五</li> | |
| <li class="calendar-item week">六</li> | |
| <li | |
| class="calendar-item" | |
| v-for="day in days" | |
| :class="{today: day.isToday, selected: day.isSelected, 'prev-month': day.isPrevMonth, 'next-month': day.isNextMonth, disabled: day.isDisabled}" | |
| @click="handleDaySelect(day.value, day.isPrevMonth, day.isNextMonth, day.isDisabled)"> | |
| {{ day.value }} | |
| </li> | |
| </ul> | |
| <ul class="calendar-list month" v-show="currentView === 'month'"> | |
| <li | |
| class="calendar-item" | |
| v-for="month in months" | |
| :class="{selected: month.isSelected}" | |
| @click="handleMonthSelect(month.value)"> | |
| {{ month.value }}月 | |
| </li> | |
| </ul> | |
| <ul class="calendar-list year" v-show="currentView === 'year'"> | |
| <li | |
| class="calendar-item" | |
| v-for="year in years" | |
| :class="{selected: year.isSelected}" | |
| @click="handleYearSelect(year.value)"> | |
| {{ year.value }} | |
| </li> | |
| </ul> | |
| </div> | |
| </transition> | |
| </div> | |
| </template> | |
| <script> | |
| import moment from 'moment' | |
| import { oneOf, getType } from '@/utils' | |
| import { clickOutside } from '@/directives' | |
| const DEFAULT_FORMATS = { | |
| year: 'YYYY', | |
| month: 'YYYY-MM', | |
| date: 'YYYY-MM-DD', | |
| } | |
| export default { | |
| name: 'app-calendar', | |
| props: { | |
| format: String, | |
| options: { | |
| type: Object, | |
| default: () => ({}), | |
| }, | |
| size: { | |
| validator: value => oneOf(value, ['small', 'normal', 'large']), | |
| default: 'normal', | |
| }, | |
| placement: { | |
| validator: value => oneOf(value, ['top', 'top-start', 'top-end', 'right', 'right-start', 'right-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end']), | |
| default: 'bottom-start', | |
| }, | |
| type: { | |
| validator: value => oneOf(value, ['date', 'year', 'month', ]), | |
| default: 'date', | |
| }, | |
| value: { | |
| validator: value => moment(value).isValid(), | |
| default: () => moment(), | |
| }, | |
| }, | |
| directives: { | |
| clickOutside | |
| }, | |
| data() { | |
| return { | |
| visible: false, | |
| width: 90, | |
| height: 30, | |
| selectDate: moment(this.value), | |
| currDate: moment(this.value), | |
| currentView: this.type, | |
| } | |
| }, | |
| computed: { | |
| dropStyle() { | |
| return getDropPosition(this.width, this.height, this.placement) | |
| }, | |
| selectDateString() { | |
| return this.selectDate.format(this.format || DEFAULT_FORMATS[this.type]) | |
| }, | |
| yearString() { | |
| const firstYear = ~~(this.currDate.year() / 10) * 10 | |
| return this.currentView === 'year' ? `${firstYear}-${firstYear + 9}` : `${this.currDate.format('YYYY')}年` | |
| }, | |
| days() { | |
| return getDays(this.currDate.clone(), this.selectDate.clone(), this.options.disabledDate) | |
| }, | |
| months() { | |
| const monthArr = [] | |
| const currDate = this.currDate.clone() | |
| for (let i = 0; i < 12; i++) { | |
| currDate.month(i) | |
| monthArr.push({ | |
| value: i + 1, | |
| isSelected: currDate.isSame(this.selectDate, 'month'), | |
| }) | |
| } | |
| return monthArr | |
| }, | |
| years() { | |
| const yearArr = [] | |
| const firstYear = ~~(this.currDate.year() / 10) * 10 | |
| for (let i = 0; i < 10; i++) { | |
| yearArr.push({ | |
| value: firstYear + i, | |
| isSelected: this.selectDate.year() === firstYear + i | |
| }) | |
| } | |
| return yearArr | |
| }, | |
| }, | |
| methods: { | |
| handleToggle() { | |
| this.visible = !this.visible | |
| }, | |
| handleClose() { | |
| this.visible = false | |
| }, | |
| handleAfterLeave() { | |
| this.currDate = this.selectDate.clone() | |
| this.currentView = this.type | |
| }, | |
| handleAdd(num, key) { | |
| this.currDate = this.currDate.clone().add(num, key) | |
| }, | |
| handleDaySelect(day, isPrevMonth, isNextMonth, isDisabled) { | |
| if (isDisabled) { | |
| return | |
| } | |
| if (isPrevMonth) { | |
| this.currDate.add(-1, 'months') | |
| } | |
| if (isNextMonth) { | |
| this.currDate.add(1, 'months') | |
| } | |
| this.currDate.date(day) | |
| this.handleClose() | |
| if (!this.selectDate.isSame(this.currDate)) { | |
| this.selectDate = this.currDate.clone() | |
| this.$emit('change', this.selectDate.format(this.format || DEFAULT_FORMATS[this.type])) | |
| } | |
| }, | |
| handleMonthSelect(month) { | |
| this.currDate = this.currDate.clone().month(month - 1) | |
| if (this.type === 'month') { | |
| this.handleClose() | |
| if (!this.selectDate.isSame(this.currDate)) { | |
| this.selectDate = this.currDate.clone() | |
| this.$emit('change', this.selectDate.format(this.format || DEFAULT_FORMATS[this.type])) | |
| } | |
| } else { | |
| this.currentView = 'date' | |
| } | |
| }, | |
| handleYearSelect(year) { | |
| this.currDate = this.currDate.clone().year(year) | |
| if (this.type === 'year') { | |
| this.handleClose() | |
| if (!this.selectDate.isSame(this.currDate)) { | |
| this.selectDate = this.currDate.clone() | |
| this.$emit('change', this.selectDate.format(this.format || DEFAULT_FORMATS[this.type])) | |
| } | |
| } else { | |
| this.currentView = 'month' | |
| } | |
| }, | |
| }, | |
| mounted() { | |
| const computedStyle = window.getComputedStyle(this.$el) | |
| this.width = Number(computedStyle.width.replace('px', '')) | |
| this.height = Number(computedStyle.height.replace('px', '')) | |
| }, | |
| } | |
| function getDropPosition(offsetLeft, offsetTop, placement) { | |
| const offset = 5 | |
| const position = {} | |
| const postfix = placement.split('-')[1] | |
| if (placement.indexOf('top') > -1) { | |
| position.bottom = `${offset}px` | |
| if (postfix) { | |
| if (postfix === 'start') { | |
| position.left = '0' | |
| } | |
| if (postfix === 'end') { | |
| position.right = '0' | |
| } | |
| } else { | |
| position.left = `${offsetLeft / 2}` | |
| position.transform = 'translate3d(-50%, 0, 0)' | |
| } | |
| } | |
| if (placement.indexOf('right') > -1) { | |
| position.left = `${offsetLeft + offset}px` | |
| if (postfix) { | |
| if (postfix === 'start') { | |
| position.top = '0' | |
| } | |
| if (postfix === 'end') { | |
| position.bottom = '0' | |
| } | |
| } else { | |
| position.top = `${offsetTop / 2}` | |
| position.transform = 'translate3d(0, -50%, 0)' | |
| } | |
| } | |
| if (placement.indexOf('bottom') > -1) { | |
| position.top = `${(offsetTop + offset)}px` | |
| if (postfix) { | |
| if (postfix === 'start') { | |
| position.left = '0' | |
| } | |
| if (postfix === 'end') { | |
| position.right = '0' | |
| } | |
| } else { | |
| position.left = `${offsetLeft / 2}` | |
| position.transform = 'translate3d(-50%, 0, 0)' | |
| } | |
| } | |
| if (placement.indexOf('left') > -1) { | |
| position.right = `${offsetLeft + offset}px` | |
| if (postfix) { | |
| if (postfix === 'start') { | |
| position.top = '0' | |
| } | |
| if (postfix === 'end') { | |
| position.bottom = '0' | |
| } | |
| } else { | |
| position.top = `${offsetTop / 2}` | |
| position.transform = 'translate3d(0, -50%, 0)' | |
| } | |
| } | |
| return position | |
| } | |
| function getDays(currDate, selectDate, disabledDate) { | |
| const today = moment() | |
| const firstDayInWeek = moment(currDate.format('YYYY-MM') + '-01').day() | |
| const days = currDate.daysInMonth() | |
| const prevMonthDays = firstDayInWeek === 0 ? 7 : firstDayInWeek | |
| const nextMonthDays = 42 - prevMonthDays - days | |
| const dayArr = [] | |
| const prevMonth = currDate.clone().add(-1, 'months') | |
| const nextMonth = currDate.clone().add(1, 'months') | |
| const lastDayOfPrevMonth = prevMonth.daysInMonth() | |
| for (let i = lastDayOfPrevMonth; i > lastDayOfPrevMonth - prevMonthDays; i--) { | |
| prevMonth.date(i) | |
| dayArr.unshift({ | |
| value: i, | |
| isPrevMonth: true, | |
| isDisabled: disabledDate && getType(disabledDate) === 'function' && disabledDate(prevMonth.toDate()), | |
| }) | |
| } | |
| for (let i = 1; i <= days; i++) { | |
| currDate.date(i) | |
| dayArr.push({ | |
| value: i, | |
| isSelected: currDate.isSame(selectDate, 'day'), | |
| isToday: currDate.isSame(today, 'day'), | |
| isDisabled: disabledDate && getType(disabledDate) === 'function' && disabledDate(currDate.toDate()), | |
| }) | |
| } | |
| for (let i = 1; i <= nextMonthDays; i++) { | |
| nextMonth.date(i) | |
| dayArr.push({ | |
| value: i, | |
| isNextMonth: true, | |
| isDisabled: disabledDate && getType(disabledDate) === 'function' && disabledDate(nextMonth.toDate()), | |
| }) | |
| } | |
| return dayArr | |
| } | |
| </script> | |
| <style lang="scss" scoped> | |
| .calendar{ | |
| &-wrap{ | |
| position: relative; | |
| display: inline-block; | |
| width: 90px; | |
| height: 30px; | |
| color: #4ccaac; | |
| font-size: 12px; | |
| border: 1px solid rgba(255, 255, 255, .5); | |
| border-radius: 5px; | |
| box-sizing: border-box; | |
| &.small{ | |
| height: 24px; | |
| } | |
| &.large{ | |
| height: 36px; | |
| font-size: 14px; | |
| } | |
| } | |
| &-input{ | |
| display: block; | |
| width: 100%; | |
| height: 100%; | |
| background: transparent; | |
| color: #999; | |
| text-align: center; | |
| cursor: pointer; | |
| border: 0; | |
| } | |
| &-drop{ | |
| position: absolute; | |
| width: 210px; | |
| background: #fff; | |
| color: #333; | |
| font-size: 14px; | |
| border: 1px solid #4ccaac; | |
| border-radius: 5px; | |
| box-sizing: border-box; | |
| z-index: 99999; | |
| } | |
| &-head{ | |
| height: 30px; | |
| margin-bottom: 5px; | |
| padding: 0 5px; | |
| text-align: center; | |
| line-height: 30px; | |
| overflow: hidden; | |
| border-bottom: 1px solid #ddd; | |
| %btn{ | |
| width: 20px; | |
| height: 100%; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| cursor: pointer; | |
| } | |
| .prevYear{ | |
| @extend %btn; | |
| float: left; | |
| background-image: url(images/prev2.png); | |
| } | |
| .prevMonth{ | |
| @extend %btn; | |
| float: left; | |
| background-image: url(images/prev.png); | |
| } | |
| .nextYear{ | |
| @extend %btn; | |
| float: right; | |
| background-image: url(images/next2.png); | |
| } | |
| .nextMonth{ | |
| @extend %btn; | |
| float: right; | |
| background-image: url(images/next.png); | |
| } | |
| .calendar-year, | |
| .calendar-month{ | |
| cursor: pointer; | |
| &:hover{ | |
| color: #4ccaac; | |
| } | |
| } | |
| } | |
| &-list{ | |
| width: 189px; | |
| margin: 0 auto; | |
| padding-bottom: 10px; | |
| overflow: hidden; | |
| &.month, | |
| &.year{ | |
| padding-bottom: 0; | |
| .calendar-item{ | |
| width: 63px; | |
| height: 30px; | |
| margin-bottom: 5px; | |
| } | |
| } | |
| } | |
| &-item{ | |
| float: left; | |
| width: 27px; | |
| height: 27px; | |
| line-height: 27px; | |
| text-align: center; | |
| border: 1px solid transparent; | |
| box-sizing: border-box; | |
| transition: .3s; | |
| cursor: pointer; | |
| &:hover{ | |
| background: #cdf5ef; | |
| } | |
| &.week{ | |
| color: #666; | |
| cursor: default; | |
| &:hover{ | |
| background: transparent; | |
| } | |
| } | |
| &.today{ | |
| border-color: #4ccaac; | |
| } | |
| &.selected{ | |
| color: #fff; | |
| background: #4ccaac; | |
| &:hover{ | |
| background: #4ccaac; | |
| } | |
| } | |
| &.prev-month, | |
| &.next-month{ | |
| color: #bbb; | |
| } | |
| &.disabled{ | |
| background: #f4f4f4; | |
| color: #bbb; | |
| cursor: not-allowed; | |
| } | |
| } | |
| } | |
| .slide-down-enter, | |
| .slide-down-leave-to{ | |
| transform: translate3d(0, -5px, 0); | |
| opacity: 0; | |
| } | |
| .slide-down-enter-to, | |
| .slide-down-leave{ | |
| transform: translate3d(0, 0, 0); | |
| opacity: 1; | |
| } | |
| .slide-down-enter-active, | |
| .slide-down-leave-active{ | |
| transition: .3s; | |
| } | |
| </style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment