2
0
mirror of https://github.com/tenrok/vue2-datepicker.git synced 2026-06-10 07:02:27 +03:00

添加timepicker

This commit is contained in:
mxie
2017-11-02 22:27:00 +08:00
parent 324968ca08
commit 73438e4558
4 changed files with 380 additions and 55 deletions
+7 -3
View File
@@ -48,16 +48,20 @@ export default {
| Prop | Type | Default | Description |
|-------------------|---------------|-------------|---------------------------------------------------|
| range | Boolean | false | if true, the type is daterange |
| type | String | 'date' | select datepicker or datetimepicker(date/datetime)|
| range | Boolean | false | if true, the type is daterange or datetimerange |
| confirm | Boolean | false | if true, need click the button to change the value|
| format | String | yyyy-MM-dd | Date formatting string |
| lang | String | zh | Translation (en/zh/es/pt-br/fr) |
| placeholder | String | | input placeholder text |
| width | String/Number | 210 | input size |
| disabled-days | Array | [] | Days in YYYY-MM-DD format to disable |
| not-before | String | '' | Disable all dates before date in YYY-MM-DD format |
| not-after | String | '' | Disable all dates after date in YYY-MM-DD format |
| not-before | String/Date | '' | Disable all dates before new Date(not-before) |
| not-after | String/Date | '' | Disable all dates after new Date(not-after) |
| shortcuts | Boolean/Array | true | the shortcuts for the range picker |
| first-day-of-week | Number | 7 | set the first day of week (1-7) |
| minute-step | Number | 0 | if > 0 don't show the second picker(0 - 60) |
## shortcuts
* true - show the default shortcuts
+210 -24
View File
@@ -1,23 +1,26 @@
<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>
<a v-if="currentPanel === 'time'" @click="currentPanel = 'date'">{{now.toLocaleDateString()}}</a>
<template v-else>
<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>
</template>
</div>
<div class="calendar-content">
<table class="calendar-table" v-show="currentPanel === 'date'">
<thead>
<tr>
<th v-for="day in days">{{day}}</th>
<th v-for="(day, index) in days" :key="index">{{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>
<td v-for="cell in row" :title="cell.title" :class="getDateClasses(cell)" @click="selectDate(cell)">{{cell.day}}</td>
</tr>
</tbody>
</table>
@@ -27,11 +30,33 @@
<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 class="calendar-time"
v-show="currentPanel === 'time'" >
<div class="time-list-wrapper"
:style="{width: 100 / times.length + '%' }"
v-for="(time, index) in times"
:key="index">
<ul class="time-list">
<li class="time-item"
v-for="num in time"
:class="getTimeClasses(num, index)"
:key="num"
@click="selectTime(num, index)"
>{{num | timeText}}</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
function getTimeArray (len, step = 1) {
const length = parseInt(len / step)
return Array.apply(null, {length}).map((v, i) => i * step)
}
export default {
props: {
startAt: null,
@@ -41,15 +66,22 @@ export default {
},
data () {
const translation = this.$parent.translation
const minuteStep = this.$parent.minuteStep
const times = [getTimeArray(24, 1), getTimeArray(60, minuteStep || 1)]
if (minuteStep === 0) {
times.push(getTimeArray(60, 1))
}
return {
months: translation.months,
dates: [],
now: new Date(), // calendar-header 显示的时间, 用于切换日历
dates: [], // 日历面板
years: [], // 年代面板
currentPanel: 'date'
now: new Date(), // calendar-header 显示的时间, 用于切换日历
currentPanel: 'date',
times: times
}
},
computed: {
// 日历显示头
days () {
const days = this.$parent.translation.days
const firstday = +this.$parent.firstDayOfWeek
@@ -60,6 +92,15 @@ export default {
},
currentMonth () {
return this.now.getMonth()
},
curHour () {
return this.now.getHours()
},
curMinute () {
return this.now.getMinutes()
},
curSecond () {
return this.now.getSeconds()
}
},
created () {
@@ -78,11 +119,14 @@ export default {
},
now: 'updateCalendar'
},
filters: {
timeText (value) {
return ('00' + value).slice(String(value).length)
}
},
methods: {
updateNow () {
let now = this.value ? new Date(this.value) : new Date()
now.setDate(1)
this.now = now
this.now = this.value ? new Date(this.value) : new Date()
},
// 更新面板选择时间
updateCalendar () {
@@ -90,8 +134,9 @@ export default {
return Array.apply(null, { length }).map((v, i) => { // eslint-disable-line
let day = firstday + i
const date = new Date(time.getFullYear(), time.getMonth(), day, 0, 0, 0)
date.setDate(day)
return {
title: date.toLocaleDateString(),
title: date.toLocaleString(),
date,
day,
classes
@@ -123,17 +168,18 @@ export default {
}
this.dates = result
},
getClasses (cell) {
getDateClasses (cell) {
const classes = []
const cellTime = cell.date.getTime()
const cellTime = new Date(cell.date).setHours(0, 0, 0, 0)
const cellEndTime = new Date(cell.date).setHours(23, 59, 59, 999)
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)
if (this.$parent.disabledDays.some(v => +new Date(v) === +cell.date) ||
(this.$parent.notBefore !== '' && cell.date.getTime() < (new Date(this.$parent.notBefore)).getTime()) ||
(this.$parent.notAfter !== '' && cell.date.getTime() > (new Date(this.$parent.notAfter)).getTime())) {
(this.$parent.notBefore !== '' && cellEndTime < (new Date(this.$parent.notBefore)).getTime()) ||
(this.$parent.notAfter !== '' && cellTime > (new Date(this.$parent.notAfter)).getTime())) {
return 'disabled'
}
@@ -161,6 +207,46 @@ export default {
}
return classes.join(' ')
},
getTimeClasses (value, index) {
let curValue
let cellTime
const startTime = this.startAt ? new Date(this.startAt) : 0
const endTime = this.endAt ? new Date(this.endAt) : 0
const classes = []
switch (index) {
case 0:
curValue = this.curHour
cellTime = new Date(this.now).setHours(value)
break
case 1:
curValue = this.curMinute
cellTime = new Date(this.now).setMinutes(value)
break
case 2:
curValue = this.curSecond
cellTime = new Date(this.now).setSeconds(value)
break
}
if (
this.$parent.notBefore !== '' && cellTime < new Date(this.$parent.notBefore).getTime() ||
this.$parent.notAfter !== '' && cellTime > new Date(this.$parent.notAfter).getTime()
) {
return 'disabled'
}
if (value === curValue) {
classes.push('cur-time')
} else if (startTime) {
if (cellTime < startTime) {
classes.push('disabled')
}
} else if (endTime) {
if (cellTime > endTime) {
classes.push('disabled')
}
}
return classes.join(' ')
},
showMonths () {
if (this.currentPanel === 'months') {
this.currentPanel = 'date'
@@ -188,21 +274,41 @@ export default {
this.years = this.years.map(v => v + flag * 10)
} else {
const now = new Date(this.now)
now.setFullYear(now.getFullYear() + flag)
now.setFullYear(now.getFullYear() + flag, now.getMonth(), 1)
this.now = now
}
},
changeMonth (flag) {
const now = new Date(this.now)
now.setMonth(now.getMonth() + flag)
now.setMonth(now.getMonth() + flag, 1)
this.now = now
},
selectDate (cell) {
const classes = this.getClasses(cell)
const classes = this.getDateClasses(cell)
if (classes.indexOf('disabled') !== -1) {
return
}
this.$emit('input', cell.date)
let date = new Date(cell.date)
// datetime 跳转到 timepicker
if (this.$parent.type === 'datetime') {
// 保留时分秒
if (this.value instanceof Date) {
date.setHours(this.value.getHours(), this.value.getMinutes(), this.value.getSeconds())
}
if (this.startAt && date.getTime() < new Date(this.startAt).getTime()){
date = new Date(this.startAt)
} else if (this.endAt && date.getTime() > new Date(this.endAt).getTime()) {
date = new Date(this.endAt)
}
this.currentPanel = 'time'
this.$nextTick(() => {
Array.prototype.forEach.call(this.$el.querySelectorAll('.cur-time'), function (el) {
el.scrollIntoView()
})
})
}
this.now = date
this.$emit('input', date)
this.$emit('select')
},
selectYear (year) {
@@ -216,6 +322,19 @@ export default {
now.setMonth(month)
this.now = now
this.currentPanel = 'date'
},
selectTime (value, index) {
const date = new Date(this.now)
if (index === 0) {
date.setHours(value)
} else if (index === 1) {
date.setMinutes(value)
} else if (index === 2) {
date.setSeconds(value)
}
this.now = date
this.$emit('input', date)
this.$emit('select')
}
}
}
@@ -224,6 +343,7 @@ export default {
<style scoped>
.calendar {
float: left;
width: 100%;
padding: 6px 12px;
}
@@ -309,7 +429,8 @@ export default {
}
.calendar-year,
.calendar-month {
.calendar-month,
.calendar-time {
width: 100%;
height: 224px;
padding: 7px 0;
@@ -329,4 +450,69 @@ export default {
line-height: 40px;
margin: 8px 1.5%;
}
.time-list-wrapper {
display: inline-block;
width: 50%;
height: 100%;
border-top: 1px solid rgba(0, 0, 0, 0.05);
border-left: 1px solid rgba(0, 0, 0, 0.05);
box-sizing: border-box;
overflow-y: auto;
}
.time-list-wrapper::-webkit-scrollbar {
width: 8px;
height: 8px;
}
/* 滚动条滑块 */
.time-list-wrapper::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.05);
border-radius: 10px;
box-shadow: inset 1px 1px 0 rgba(0, 0, 0, 0.1);
}
.time-list-wrapper:hover::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
}
.time-list-wrapper:first-child {
border-left: 0;
}
.time-list {
margin: 0;
padding: 0;
list-style: none;
text-align: center;
}
.time-item {
width: 100%;
font-size: 12px;
height: 30px;
line-height: 30px;
cursor: pointer;
}
.time-item:hover {
background-color: #eaf8fe;
}
.time-item.cur-time {
color: #fff;
background-color: #1284e7;
}
.time-item.disabled {
cursor: not-allowed;
color: #ccc;
background-color: #f3f3f3;
}
/* .time-hint {
position: sticky;
top: 0px;
line-height: 30px;
color: #ccc;
background: #fff;
} */
</style>
+69 -18
View File
@@ -1,6 +1,6 @@
<template>
<div class="datepicker"
:style="{'width': width + 'px','min-width':range ? '210px' : '140px'}"
:style="{'width': width + 'px','min-width':range ? (type === 'datetime' ? '320px' : '210px') : '140px'}"
v-clickoutside="closePopup">
<input readonly
class="input"
@@ -20,9 +20,10 @@
ref="calendar"
v-show="showPopup">
<template v-if="!range">
<calendar-panel @select="showPopup = false"
v-model="currentValue"
:show="showPopup"></calendar-panel>
<calendar-panel
v-model="currentValue"
@select="selectDate"
:show="showPopup"></calendar-panel>
</template>
<template v-else>
<div class="datepicker-top" v-if="ranges.length">
@@ -31,12 +32,17 @@
<calendar-panel style="width:50%;box-shadow:1px 0 rgba(0, 0, 0, .1)"
v-model="currentValue[0]"
:end-at="currentValue[1]"
@select="selectDate"
:show="showPopup"></calendar-panel>
<calendar-panel style="width:50%;"
v-model="currentValue[1]"
:start-at="currentValue[0]"
@select="selectDate"
:show="showPopup"></calendar-panel>
</template>
<div class="datepicker-footer" v-if="confirm">
<button type="button" class="datepicker-btn datepicker-btn-confirm" @click="confirmDate">确定</button>
</div>
</div>
</div>
</template>
@@ -48,6 +54,7 @@ import Languages from './languages.js'
export default {
components: { CalendarPanel },
props: {
value: null,
format: {
type: String,
default: 'yyyy-MM-dd'
@@ -56,6 +63,10 @@ export default {
type: Boolean,
default: false
},
type: {
type: String,
default: 'date' // ['date', 'datetime']
},
width: {
type: [String, Number],
default: 210
@@ -65,7 +76,6 @@ export default {
type: String,
default: 'zh'
},
value: null,
shortcuts: {
type: [Boolean, Array],
default: true
@@ -75,17 +85,24 @@ export default {
default: function () { return [] }
},
notBefore: {
type: String,
default: ''
},
notAfter: {
type: String,
default: ''
},
firstDayOfWeek: {
default: 7,
type: Number,
validator: val => val >= 1 && val <= 7
},
minuteStep: {
type: Number,
default: 0,
validator: val => val >= 0 && val <= 60
},
confirm: {
type: Boolean,
default: false
}
},
data () {
@@ -103,16 +120,11 @@ export default {
if (!this.range) {
this.currentValue = this.isValidDate(val) ? val : undefined
} else {
this.currentValue = this.isValidRange(val) ? val : [undefined, undefined]
this.currentValue = this.isValidRange(val) ? val.slice(0, 2) : [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)
@@ -127,16 +139,36 @@ export default {
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.isValidDate(this.value)) {
return this.stringify(this.value)
}
if (this.range && this.currentValue[0] && this.currentValue[1]) {
return this.stringify(this.currentValue[0]) + ' ~ ' + this.stringify(this.currentValue[1])
if (this.range && this.isValidRange(this.value)) {
return this.stringify(this.value[0]) + ' ~ ' + this.stringify(this.value[1])
}
return ''
}
},
methods: {
updateDate () {
const val = this.currentValue
if ((!this.range && val) || (this.range && val[0] && val[1])) {
this.$emit('input', val)
console.log(this.value, this.currentValue)
}
},
confirmDate () {
this.updateDate()
this.closePopup()
this.$emit('confirm')
},
selectDate () {
if (!this.confirm) {
this.updateDate()
if (this.type === 'date' && !this.range) {
this.closePopup()
}
}
},
closePopup () {
this.showPopup = false
},
@@ -342,7 +374,7 @@ export default {
}
.datepicker-top {
margin: 0 12px;
padding: 0 12px;
line-height: 34px;
border-bottom: 1px solid rgba(0, 0, 0, .05);
}
@@ -361,4 +393,23 @@ export default {
margin: 0 10px;
color: #48576a;
}
.datepicker-footer {
padding: 4px;
clear: both;
text-align: right;
border-top: 1px solid rgba(0, 0, 0, .05);
}
.datepicker-btn {
font-size: 12px;
line-height: 28px;
padding: 0 5px;
margin: 0 5px;
cursor: pointer;
background-color: transparent;
outline: none;
border: none;
}
.datepicker-btn-confirm {
color: #1284e7;
}
</style>
+94 -10
View File
@@ -1,13 +1,64 @@
<template>
<div id="app">
<div class="demo">
<span class="label">default:</span>
<date-picker v-model="value1" lang="en" :first-day-of-week="1"></date-picker>
<div class="example">
<section class="demo">
<span class="label">default:</span>
<date-picker v-model="value1" lang="en"></date-picker>
</section>
<section class="demo">
<span class="label">range:</span>
<date-picker v-model="value2" range lang="zh"></date-picker>
</section>
<pre class="pre">{{demo1}}</pre>
</div>
<div class="demo">
<span class="label">range:</span>
<date-picker v-model="value2" range lang="zh" :disabled-days="disabledDays"></date-picker>
<div class="example">
<section class="demo">
<span class="label">datetime:</span>
<date-picker v-model="value3" type="datetime" format="yyyy-MM-dd HH:mm:ss"></date-picker>
</section>
<section class="demo">
<span class="label">datetime with minute-step picker:</span>
<date-picker v-model="value4" type="datetime" format="yyyy-MM-dd HH:mm" :minute-step="10"></date-picker>
</section>
<section class="demo">
<span class="label">datetime range:</span>
<date-picker v-model="value5" range type="datetime" lang="zh" format="yyyy-MM-dd HH:mm:ss"></date-picker>
</section>
<blockquote class="tips">
if you use the datetime, you should set the format which default is 'yyyy-MM-dd'
</blockquote>
<pre class="pre">{{demo2}}</pre>
</div>
<div class="example">
<section class="demo">
<span class="label">with confirm:</span>
<date-picker
v-model="value6"
format="yyyy-MM-dd"
confirm></date-picker>
</section>
<section class="demo">
<span class="label">datetime with confirm:</span>
<date-picker
v-model="value7"
type="datetime"
format="yyyy-MM-dd hh:mm:ss"
confirm></date-picker>
</section>
<section class="demo">
<span class="label">range with confirm:</span>
<date-picker
v-model="value8"
range
format="yyyy-MM-dd"
confirm></date-picker>
</section>
<blockquote class="tips">
Recommend to use the confirm option when the type is 'datetime' or range is true
</blockquote>
<pre class="pre">{{demo3}}</pre>
</div>
</div>
</template>
@@ -20,8 +71,16 @@ export default {
data () {
return {
value1: new Date(),
disabledDays: ['2017-6-13'],
value2: ''
value2: '',
value3: new Date(),
value4: '',
value5: '',
value6: '',
value7: '',
value8: '',
demo1: '<date-picker v-model="value1" lang="en"></date-picker>\n<date-picker v-model="value3" range></date-picker>',
demo2: '<date-picker v-model="value3" type="datetime" format="yyyy-MM-dd HH:mm:ss"></date-picker>\n<date-picker v-model="value4" type="datetime" format="yyyy-MM-dd HH:mm" :minute-step="10"></date-picker>\n<date-picker v-model="value4" range type="datetime" lang="zh" format="yyyy-MM-dd HH:mm:ss"></date-picker>',
demo3: '<date-picker v-model="value6" format="yyyy-MM-dd" confirm></date-picker>\n<date-picker v-model="value7" type="datetime" format="yyyy-MM-dd hh:mm:ss" confirm></date-picker>\n<date-picker v-model="value8" range format="yyyy-MM-dd" confirm></date-picker>'
}
}
}
@@ -29,10 +88,35 @@ export default {
<style>
.demo {
float:left;
margin:250px;
margin:20px;
}
.label{
display: inline-block;
margin-right: 1em;
}
.pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f6f8fa;
border-radius: 3px;
}
.example {
padding: 45px;
word-wrap: break-word;
background-color: #fff;
border: 1px solid #ddd;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
.example > .demo {
display: inline-block;
}
.tips {
margin: 0;
padding: 0 1em;
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
}
</style>