<template> <picker ref="picker" show-toolbar :columns="columns" :visible-item-count="visibleItemCount" @change="onChange" @confirm="onConfirm" @cancel="$emit('cancel')" /> </template> <script> import { create } from '../utils'; import Picker from '../picker'; const isValidDate = date => Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime()); export default create({ name: 'van-datetime-picker', components: { Picker }, props: { type: { type: String, default: 'datetime' }, format: { type: String, default: 'YYYY.MM.DD HH时 mm分' }, visibleItemCount: { type: Number, default: 5 }, minDate: { type: Date, default() { return new Date(new Date().getFullYear() - 10, 0, 1); }, validator: isValidDate }, maxDate: { type: Date, default() { return new Date(new Date().getFullYear() + 10, 11, 31); }, validator: isValidDate }, minHour: { type: Number, default: 0 }, maxHour: { type: Number, default: 23 }, value: {} }, data() { return { innerValue: this.correctValue(this.value) }; }, watch: { value(val) { val = this.correctValue(val); const isEqual = this.type === 'time' ? val === this.innerValue : val.valueOf() === this.innerValue.valueOf(); if (!isEqual) this.innerValue = val; }, innerValue(val) { this.updateColumnValue(val); this.$emit('input', val); } }, computed: { ranges() { if (this.type === 'time') { return [ [this.minHour, this.maxHour], [0, 59] ]; } const { maxYear, maxDate, maxMonth, maxHour, maxMinute } = this.getBoundary('max', this.innerValue); const { minYear, minDate, minMonth, minHour, minMinute } = this.getBoundary('min', this.innerValue); const result = [ [minYear, maxYear], [minMonth, maxMonth], [minDate, maxDate], [minHour, maxHour], [minMinute, maxMinute] ]; if (this.type === 'date') result.splice(3, 2); return result; }, columns() { const results = this.ranges.map(range => { const values = this.times(range[1] - range[0] + 1, index => { const value = range[0] + index; return value < 10 ? `0${value}` : `${value}`; }); return { values }; }); return results; } }, methods: { correctValue(value) { // validate value const isDateType = this.type.indexOf('date') > -1; if (isDateType && !isValidDate(value)) { value = this.minDate; } else if (!value) { const { minHour } = this; value = `${minHour > 10 ? minHour : '0' + minHour}:00`; } // time type if (!isDateType) { const [hour, minute] = value.split(':'); let correctedHour = Math.max(hour, this.minHour); correctedHour = Math.min(correctedHour, this.maxHour); return `${correctedHour}:${minute}`; } // date type const { maxYear, maxDate, maxMonth, maxHour, maxMinute } = this.getBoundary('max', value); const { minYear, minDate, minMonth, minHour, minMinute } = this.getBoundary('min', value); const minDay = new Date(minYear, minMonth - 1, minDate, minHour, minMinute); const maxDay = new Date(maxYear, maxMonth - 1, maxDate, maxHour, maxMinute); value = Math.max(value, minDay); value = Math.min(value, maxDay); return new Date(value); }, times(n, iteratee) { let index = -1; const result = Array(n); while (++index < n) { result[index] = iteratee(index); } return result; }, getBoundary(type, value) { const boundary = this[`${type}Date`]; const year = boundary.getFullYear(); let month = 1; let date = 1; let hour = 0; let minute = 0; if (type === 'max') { month = 12; date = this.getMonthEndDay(value.getFullYear(), value.getMonth() + 1); hour = 23; minute = 59; } if (value.getFullYear() === year) { month = boundary.getMonth() + 1; if (value.getMonth() + 1 === month) { date = boundary.getDate(); if (value.getDate() === date) { hour = boundary.getHours(); if (value.getHours() === hour) { minute = boundary.getMinutes(); } } } } return { [`${type}Year`]: year, [`${type}Month`]: month, [`${type}Date`]: date, [`${type}Hour`]: hour, [`${type}Minute`]: minute }; }, getTrueValue(formattedValue) { if (!formattedValue) return; while (isNaN(parseInt(formattedValue, 10))) { formattedValue = formattedValue.slice(1); } return parseInt(formattedValue, 10); }, getMonthEndDay(year, month) { if (this.isShortMonth(month)) { return 30; } else if (month === 2) { return this.isLeapYear(year) ? 29 : 28; } else { return 31; } }, isLeapYear(year) { return (year % 400 === 0) || (year % 100 !== 0 && year % 4 === 0); }, isShortMonth(month) { return [4, 6, 9, 11].indexOf(month) > -1; }, onConfirm() { this.$emit('confirm', this.innerValue); }, onChange(picker) { const values = picker.getValues(); let value; if (this.type === 'time') { value = values.join(':'); } else { const year = this.getTrueValue(values[0]); const month = this.getTrueValue(values[1]); const maxDate = this.getMonthEndDay(year, month); let date = this.getTrueValue(values[2]); date = date > maxDate ? maxDate : date; let hour = 0; let minute = 0; if (this.type === 'datetime') { hour = this.getTrueValue(values[3]); minute = this.getTrueValue(values[4]); } value = new Date(year, month - 1, date, hour, minute); } value = this.correctValue(value); this.innerValue = value; this.$emit('change', picker); }, updateColumnValue(value) { let values = []; if (this.type === 'time') { const currentValue = value.split(':'); values = [ currentValue[0], currentValue[1] ]; } else { values = [ `${value.getFullYear()}`, `0${value.getMonth() + 1}`.slice(-2), `0${value.getDate()}`.slice(-2) ]; if (this.type === 'datetime') { values.push( `0${value.getHours()}`.slice(-2), `0${value.getMinutes()}`.slice(-2) ); } } this.$nextTick(() => { this.setColumnByValues(values); }); }, setColumnByValues(values) { if (!this.$refs.picker) { return; } this.$refs.picker.setValues(values); } }, mounted() { this.updateColumnValue(this.innerValue); } }); </script>