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