2
0
mirror of https://github.com/tenrok/vue2-datepicker.git synced 2026-06-21 21:20:35 +03:00
This commit is contained in:
mengxiong10
2017-05-26 14:23:09 +08:00
parent 3342b27e0e
commit 982f56b48f
8 changed files with 16 additions and 5 deletions
-37
View File
@@ -1,37 +0,0 @@
<template>
<div id="app">
<div class="demo">
<span class="label">default:</span>
<date-picker v-model="value1" lang="en"></date-picker>
</div>
<div class="demo">
<span class="label">range:</span>
<date-picker v-model="value2" range lang="zh"></date-picker>
</div>
</div>
</template>
<script>
import DatePicker from './datepicker/index.vue'
export default {
name: 'app',
components: { DatePicker },
data () {
return {
value1: '2017-5-9',
value2: ''
}
}
}
</script>
<style>
.demo {
float:left;
margin:250px;
}
.label{
margin-right: 1em;
}
</style>
-322
View File
@@ -1,322 +0,0 @@
<template>
<div class="calendar">
<div class="calendar-header">
<a class="calendar__prev-icon" @click="changeYear(-1)">&laquo;</a>
<a v-show="currentPanel === 'date'" class="calendar__prev-icon" @click="changeMonth(-1)">&lsaquo;</a>
<a class="calendar__next-icon" @click="changeYear(1)">&raquo;</a>
<a v-show="currentPanel === 'date'" class="calendar__next-icon" @click="changeMonth(1)" >&rsaquo;</a>
<a @click="showMonths">{{months[currentMonth]}}</a>
<a @click="showYears">{{currentYear}}</a>
</div>
<div class="calendar-content">
<table class="calendar-table" v-show="currentPanel === 'date'">
<thead>
<tr>
<th v-for="day in days">{{day}}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in dates">
<td v-for="cell in row"
:title="cell.title"
:class="getClasses(cell)"
@click="selectDate(cell)">{{cell.day}}</td>
</tr>
</tbody>
</table>
<div class="calendar-year" v-show="currentPanel === 'years'">
<a v-for="year in years" @click="selectYear(year)" :class="{'current': currentYear === year}">{{year}}</a>
</div>
<div class="calendar-month" v-show="currentPanel === 'months'">
<a v-for="(month, index) in months" @click="selectMonth(index)" :class="{'current': currentMonth === index}">{{month}}</a>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
startAt: null,
endAt: null,
value: null,
show: Boolean
},
data () {
const translation = this.$parent.translation
return {
days: translation.days,
months: translation.months,
dates: [],
now: new Date(), // calendar-header 显示的时间, 用于切换日历
years: [], // 年代面板
currentPanel: 'date'
}
},
computed: {
currentYear () {
return this.now.getFullYear()
},
currentMonth () {
return this.now.getMonth()
}
},
created () {
this.updateCalendar()
},
watch: {
show (val) {
if (val) {
this.currentPanel = 'date'
this.updateNow()
}
},
value: {
handler: 'updateNow',
immediate: true
},
now: 'updateCalendar'
},
methods: {
updateNow () {
let now = this.value ? new Date(this.value) : new Date()
now.setDate(1)
this.now = now
},
// 更新面板选择时间
updateCalendar () {
function getCalendar (time, firstday, length, classes) {
return Array.apply(null, { length }).map((v, i) => { // eslint-disable-line
let day = firstday + i
const date = new Date(time.getFullYear(), time.getMonth(), day)
return {
title: date.toLocaleDateString(),
date,
day,
classes
}
})
}
const time = new Date(this.now)
time.setDate(0) // 把时间切换到上个月最后一天
const lastMonthLength = time.getDay() + 1 // time.getDay() 0是星期天, 1是星期一 ...
const lastMonthfirst = time.getDate() - (lastMonthLength - 1)
const lastMonth = getCalendar(time, lastMonthfirst, lastMonthLength, 'lastMonth')
time.setMonth(time.getMonth() + 2, 0) // 切换到这个月最后一天
const curMonthLength = time.getDate()
const curMonth = getCalendar(time, 1, curMonthLength, 'curMonth')
time.setMonth(time.getMonth() + 1, 1)
const nextMonthLength = 42 - (lastMonthLength + curMonthLength)
const nextMonth = getCalendar(time, 1, nextMonthLength, 'nextMonth')
// 分割数组
let index = 0
let resIndex = 0
const arr = lastMonth.concat(curMonth, nextMonth)
const result = new Array(6)
while (index < 42) {
result[resIndex++] = arr.slice(index, index += 7)
}
this.dates = result
},
getClasses (cell) {
const classes = []
const cellTime = cell.date.getTime()
const curTime = this.value ? new Date(this.value).setHours(0, 0, 0, 0) : 0
const startTime = this.startAt ? new Date(this.startAt).setHours(0, 0, 0, 0) : 0
const endTime = this.endAt ? new Date(this.endAt).setHours(0, 0, 0, 0) : 0
const today = new Date().setHours(0, 0, 0, 0)
classes.push(cell.classes)
if (cellTime === today) {
classes.push('today')
}
// range classes
if (cellTime === curTime) {
classes.push('current')
} else if (startTime) {
if (cellTime < startTime) {
classes.push('disabled')
} else if (curTime && cellTime <= curTime) {
classes.push('inrange')
}
} else if (endTime) {
if (cellTime > endTime) {
classes.push('disabled')
} else if (curTime && cellTime >= curTime) {
classes.push('inrange')
}
}
return classes.join(' ')
},
showMonths () {
if (this.currentPanel === 'months') {
this.currentPanel = 'date'
} else {
this.currentPanel = 'months'
}
},
showYears () {
// 当前年代
if (this.currentPanel === 'years') {
this.currentPanel = 'date'
} else {
let firstYear = Math.floor(this.now.getFullYear() / 10) * 10
let years = []
for (let i = 0; i < 10; i++) {
years.push(firstYear + i)
}
this.years = years
this.currentPanel = 'years'
}
},
// 前进或后退一年
changeYear (flag) {
if (this.currentPanel === 'years') {
this.years = this.years.map(v => v + flag * 10)
} else {
const now = new Date(this.now)
now.setFullYear(now.getFullYear() + flag)
this.now = now
}
},
changeMonth (flag) {
const now = new Date(this.now)
now.setMonth(now.getMonth() + flag)
this.now = now
},
selectDate (cell) {
const classes = this.getClasses(cell)
if (classes.indexOf('disabled') !== -1) {
return
}
this.$emit('input', cell.date)
},
selectYear (year) {
const now = new Date(this.now)
now.setFullYear(year)
this.now = now
this.currentPanel = 'months'
},
selectMonth (month) {
const now = new Date(this.now)
now.setMonth(month)
this.now = now
this.currentPanel = 'date'
}
}
}
</script>
<style scoped>
.calendar {
float: left;
padding: 6px 12px;
}
.calendar * {
box-sizing: border-box;
}
.calendar a {
color: inherit;
text-decoration: none;
cursor: pointer;
}
.calendar-header {
line-height: 34px;
text-align: center;
}
.calendar__next-icon,
.calendar__prev-icon {
font-size: 20px;
padding: 0 6px;
}
.calendar-header > a:hover {
color: #1284e7;
}
.calendar__prev-icon {
float: left;
}
.calendar__next-icon {
float: right;
}
.calendar-table {
width: 100%;
font-size: 12px;
table-layout: fixed;
border-collapse: collapse;
border-spacing: 0;
}
.calendar-table td,
.calendar-table th {
width: 32px;
height: 32px;
text-align: center;
}
.calendar-table td {
cursor: pointer;
}
.calendar-table td.inrange,
.calendar-table td:hover,
.calendar-year > a:hover,
.calendar-month > a:hover {
background-color: #eaf8fe;
}
.calendar-table td.current,
.calendar-year > a.current,
.calendar-month > a.current {
color: #fff;
background-color: #1284e7;
}
.calendar-table td.disabled {
cursor: not-allowed;
color: #ccc;
background-color: #f3f3f3;
}
.lastMonth,
.nextMonth {
color: #ddd;
}
.today {
color: #20a0ff;
}
.calendar-year,.calendar-month {
width: 100%;
height: 224px;
padding: 7px 0;
text-align: center;
}
.calendar-year > a {
display: inline-block;
width: 40%;
margin: 1px 5%;
line-height: 40px;
}
.calendar-month > a {
display: inline-block;
width: 30%;
line-height: 40px;
margin: 8px 1.5%;
}
</style>
-330
View File
@@ -1,330 +0,0 @@
<template>
<div class="datepicker"
:style="{'width': width + 'px','min-width':range ? '210px' : '140px'}"
v-clickoutside="closePopup">
<input readonly
class="input"
:value="text"
:placeholder="innerPlaceholder"
ref="input"
@click="togglePopup"
@mousedown="$event.preventDefault()">
<i class="input-icon"
:class="showCloseIcon ? 'input-icon__close' : 'input-icon__calendar'"
@mouseenter="hoverIcon"
@mouseleave="hoverIcon"
@click="clickIcon" ></i>
<div class="datepicker-popup"
:class="{'range':range}"
:style="position"
ref="calendar"
v-show="showPopup">
<template v-if="!range">
<calendar-panel v-model="currentValue" :show="showPopup"></calendar-panel>
</template>
<template v-else>
<div class="datepicker-top">
<span v-for="range in ranges" @click="selectRange(range)">{{range.text}}</span>
</div>
<calendar-panel style="width:50%;box-shadow:1px 0 rgba(0, 0, 0, .1)" v-model="currentValue[0]" :end-at="currentValue[1]" :show="showPopup"></calendar-panel>
<calendar-panel style="width:50%;" v-model="currentValue[1]" :start-at="currentValue[0]" :show="showPopup"></calendar-panel>
</template>
</div>
</div>
</template>
<script>
import CalendarPanel from './calendar-panel.vue'
import Languages from './languages.js'
export default {
components: { CalendarPanel },
props: {
format: {
type: String,
default: 'yyyy-MM-dd'
},
range: {
type: Boolean,
default: false
},
width: {
type: [String, Number],
default: 210
},
placeholder: String,
lang: {
type: String,
default: 'zh'
},
value: null
},
data () {
return {
showPopup: false,
showCloseIcon: false,
currentValue: this.value,
position: null,
ranges: [] // 快捷选项
}
},
watch: {
value: {
handler (val) {
if (!this.range) {
this.currentValue = this.isValidDate(val) ? val : undefined
} else {
this.currentValue = this.isValidRange(val) ? val : [undefined, undefined]
}
},
immediate: true
},
currentValue (val) {
if ((!this.range && val) || (this.range && val[0] && val[1])) {
this.$emit('input', val)
}
},
showPopup (val) {
if (val) {
this.$nextTick(this.displayPopup)
// this.displayPopup()
}
}
},
computed: {
translation () {
return Languages[this.lang] || Languages['en']
},
innerPlaceholder () {
return this.placeholder || (this.range ? this.translation.placeholder.dateRange : this.translation.placeholder.date)
},
text () {
if (!this.range && this.currentValue) {
return this.stringify(this.currentValue)
}
if (this.range && this.currentValue[0] && this.currentValue[1]) {
return this.stringify(this.currentValue[0]) + ' ~ ' + this.stringify(this.currentValue[1])
}
return ''
}
},
methods: {
closePopup () {
this.showPopup = false
},
togglePopup () {
if (this.showPopup) {
this.$refs.input.blur()
this.showPopup = false
} else {
this.$refs.input.focus()
this.showPopup = true
}
},
hoverIcon (e) {
if (e.type === 'mouseenter' && this.text) {
this.showCloseIcon = true
}
if (e.type === 'mouseleave') {
this.showCloseIcon = false
}
},
clickIcon () {
if (this.showCloseIcon) {
this.$emit('input', '')
} else {
this.togglePopup()
}
},
formatDate (date, fmt) {
const map = {
'M+': date.getMonth() + 1, // 月份
'[Dd]+': date.getDate(), // 日
'[Hh]+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
'S': date.getMilliseconds() // 毫秒
}
let str = fmt.replace(/[Yy]+/g, function (str) {
return ('' + date.getFullYear()).slice(4 - str.length)
})
Object.keys(map).forEach((key) => {
str = str.replace(new RegExp(key), function (str) {
const value = '' + map[key]
return str.length === 1 ? value : ('00' + value).slice(value.length)
})
})
return str
},
stringify (date) {
return this.formatDate(new Date(date), this.format)
},
isValidDate (date) {
return !!new Date(date).getTime()
},
isValidRange (date) {
return Array.isArray(date) &&
date.length === 2 &&
this.isValidDate(date[0]) &&
this.isValidDate(date[1])
},
selectRange (range) {
this.$emit('input', [range.start, range.end])
},
initRanges () {
this.ranges = [{
text: '未来7天',
start: new Date(),
end: new Date(Date.now() + 3600 * 1000 * 24 * 7)
}, {
text: '未来30天',
start: new Date(),
end: new Date(Date.now() + 3600 * 1000 * 24 * 30)
}, {
text: '最近7天',
start: new Date(Date.now() - 3600 * 1000 * 24 * 7),
end: new Date()
}, {
text: '最近30天',
start: new Date(Date.now() - 3600 * 1000 * 24 * 30),
end: new Date()
}]
this.ranges.forEach((v, i) => {
v.text = this.translation.pickers[i]
})
},
displayPopup () {
const dw = document.documentElement.clientWidth
const dh = document.documentElement.clientHeight
const InputRect = this.$el.getBoundingClientRect()
const PopupRect = this.$refs.calendar.getBoundingClientRect()
this.position = {}
if (dw - InputRect.left < PopupRect.width && InputRect.right < PopupRect.width) {
this.position.left = 1 - InputRect.left + 'px'
} else if (InputRect.left + InputRect.width / 2 <= dw / 2) {
this.position.left = 0
} else {
this.position.right = 0
}
if (InputRect.top <= PopupRect.height + 1 && dh - InputRect.bottom <= PopupRect.height + 1) {
this.position.top = dh - InputRect.top - PopupRect.height - 1 + 'px'
} else if (InputRect.top + InputRect.height / 2 <= dh / 2) {
this.position.top = '100%'
} else {
this.position.bottom = '100%'
}
}
},
created () {
this.initRanges()
},
directives: {
clickoutside: {
bind (el, binding, vnode) {
el['@clickoutside'] = (e) => {
if (!el.contains(e.target) && binding.expression && vnode.context[binding.expression]) {
binding.value()
}
}
document.addEventListener('click', el['@clickoutside'], true)
},
unbind (el) {
document.removeEventListener('click', el['@clickoutside'], true)
}
}
}
}
</script>
<style scoped>
.datepicker {
position: relative;
display: inline-block;
color:#73879c;
font: 14px/1.5 "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif;
}
.datepicker * {
box-sizing: border-box;
}
.datepicker-popup {
position: absolute;
width: 250px;
margin-top: 1px;
margin-bottom: 1px;
border: 1px solid #d9d9d9;
background-color: #fff;
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
z-index: 1000;
}
.range {
width: 496px;
}
.input {
display: inline-block;
width: 100%;
height: 34px;
padding: 6px 30px 6px 10px;
font-size: 14px;
line-height: 1.4;
color: #555;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
}
.input-icon {
top: 0;
right: 0;
position: absolute;
width: 30px;
height: 100%;
color: #888;
text-align: center;
font-style: normal;
}
.input-icon::after{
content:'';
display: inline-block;
width: 0;
height: 100%;
vertical-align: middle;
}
.input-icon__calendar{
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA00lEQVQ4T72SzQ2CQBCF54UGKIES6EAswQq0BS/A3PQ0hAt0oKVQgiVYAkcuZMwSMOyCyRKNe9uf+d6b2Qf6csGtL8sy7vu+Zebn/E5EoiAIwjRNH/PzBUBEGiJqmPniAMw+YeZkFSAiJwA3j45aVT0wsxGitwOjDGDnASBVvU4OLQARRURk9e4CAcSqWn8CLHp3Ae6MXAe/B4yzUeMkz/P9ZgdFUQzFIwD/B4yKgwMTos0OtvzCHcDRJ0gAzlmW1VYSq6oKu66LfQBTjC2AT+Hamxcml5IRpPq3VQAAAABJRU5ErkJggg==);
background-position: center;
background-repeat: no-repeat;
}
.input-icon__close::before {
content: '\2716';
vertical-align: middle;
}
.datepicker-top {
margin: 0 12px;
line-height: 34px;
border-bottom: 1px solid rgba(0, 0, 0, .05);
}
.datepicker-top>span {
white-space: nowrap;
cursor: pointer;
}
.datepicker-top>span:hover {
color: #1284e7;
}
.datepicker-top>span:after {
content: "|";
margin: 0 10px;
color: #48576a;
}
</style>
-20
View File
@@ -1,20 +0,0 @@
export default {
'zh': {
'days': ['日', '一', '二', '三', '四', '五', '六'],
'months': ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
'pickers': ['未来7天', '未来30天', '最近7天', '最近30天'],
'placeholder': {
'date': '请选择日期',
'dateRange': '请选择日期范围'
}
},
'en': {
'days': ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
'months': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
'pickers': ['next 7 days', 'next 30 days', 'previous 7 days', 'previous 30 days'],
'placeholder': {
'date': 'Select Date',
'dateRange': 'Select Date Range'
}
}
}
-7
View File
@@ -1,7 +0,0 @@
import Vue from 'vue'
import App from './App.vue'
new Vue({ // eslint-disable-line
el: '#app',
render: h => h(App)
})